home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / misc / volume40 / gnuplot / part06 < prev    next >
Encoding:
Text File  |  1993-10-21  |  80.8 KB  |  2,935 lines

  1. Newsgroups: comp.sources.misc
  2. From: woo@playfair.stanford.edu ("Alexander Woo")
  3. Subject: v40i018:  gnuplot - interactive function plotting utility, Part06/33
  4. Message-ID: <1993Oct21.144351.1696@sparky.sterling.com>
  5. X-Md4-Signature: 2b77879b8223dcedeb7c865dac58c2a9
  6. Sender: kent@sparky.sterling.com (Kent Landfield)
  7. Organization: Sterling Software
  8. Date: Thu, 21 Oct 1993 14:43:51 GMT
  9. Approved: kent@sparky.sterling.com
  10.  
  11. Submitted-by: woo@playfair.stanford.edu ("Alexander Woo")
  12. Posting-number: Volume 40, Issue 18
  13. Archive-name: gnuplot/part06
  14. Environment: UNIX, MS-DOS, VMS
  15. Supersedes: gnuplot3: Volume 24, Issue 23-48
  16.  
  17. #! /bin/sh
  18. # This is a shell archive.  Remove anything before this line, then feed it
  19. # into a shell via "sh file" or similar.  To overwrite existing files,
  20. # type "sh file -c".
  21. # Contents:  gnuplot/command.c.B gnuplot/help.c
  22. # Wrapped by kent@sparky on Wed Oct 20 17:14:39 1993
  23. PATH=/bin:/usr/bin:/usr/ucb:/usr/local/bin:/usr/lbin ; export PATH
  24. echo If this archive is complete, you will see the following message:
  25. echo '          "shar: End of archive 6 (of 33)."'
  26. if test -f 'gnuplot/command.c.B' -a "${1}" != "-c" ; then 
  27.   echo shar: Will not clobber existing file \"'gnuplot/command.c.B'\"
  28. else
  29.   echo shar: Extracting \"'gnuplot/command.c.B'\" \(57750 characters\)
  30.   sed "s/^X//" >'gnuplot/command.c.B' <<'END_OF_FILE'
  31. X    if (autoscale_lx) {
  32. X        fprintf(stderr, "Warning: empty %c range [%g:%g], ",
  33. X            parametric ? 't' : 'x', xmin, xmax);
  34. X        if (fabs(xmin) < zero) {
  35. X        /* completely arbitary */
  36. X        xmin = -1.;
  37. X        xmax = 1.;
  38. X        } else {
  39. X        /* expand range by 10% in either direction */
  40. X        xmin = xmin * 0.9;
  41. X        xmax = xmax * 1.1;
  42. X        }
  43. X        fprintf(stderr, "adjusting to [%g:%g]\n", xmin, xmax);
  44. X    } else {
  45. X        int_error("x range is less than `zero`", c_token);
  46. X    }
  47. X
  48. X    /* give error if xrange badly set from missing datafile error */
  49. X    if (xmin == VERYLARGE || xmax == -VERYLARGE) {
  50. X    int_error("x range is invalid", c_token);
  51. X    }
  52. X    if (is_log_x) {
  53. X    if (xmin <= 0.0 || xmax <= 0.0)
  54. X        int_error("x range must be greater than 0 for log scale!", NO_CARET);
  55. X    x_min = log(xmin)/log_base_log_x;
  56. X    x_max = log(xmax)/log_base_log_x;
  57. X    } else {
  58. X    x_min = xmin;
  59. X    x_max = xmax;
  60. X    }
  61. X
  62. X    xdiff = (x_max - x_min) / (samples - 1);
  63. X
  64. X    tp_ptr = &(first_plot);
  65. X    plot_num = 0;
  66. X    this_plot = first_plot;
  67. X    c_token = begin_token;    /* start over */
  68. X
  69. X    /* Read through functions */
  70. X    while (TRUE) {
  71. X    if (is_definition(c_token)) {
  72. X        define();
  73. X    } else {
  74. X        plot_num++;
  75. X        if (isstring(c_token)) {    /* data file to plot */
  76. X        /* ignore this now */
  77. X        c_token++;
  78. X
  79. X        /*
  80. X         * jev -- support for passing data from file thru user
  81. X         * function
  82. X         */
  83. X        if (almost_equals(c_token, "thru$")) {
  84. X            struct udft_entry tmp;
  85. X            c_token++;
  86. X            dummy_func = &tmp;
  87. X            (void) temp_at();
  88. X        }
  89. X        if (almost_equals(c_token, "u$sing")) {
  90. X            c_token++;    /* skip "using" */
  91. X            if (!isstring(c_token)) {
  92. X            struct value    a;
  93. X            (void) magnitude(const_express(&a));    /* skip xcol */
  94. X            if (equals(c_token, ":")) {
  95. X                c_token++;    /* skip ":" */
  96. X                (void) magnitude(const_express(&a));    /* skip ycol */
  97. X            }
  98. X            if (equals(c_token, ":")) {
  99. X                c_token++;    /* skip ":" */
  100. X                (void) magnitude(const_express(&a));    /* skip yemin */
  101. X            }
  102. X            if (equals(c_token, ":")) {
  103. X                c_token++;    /* skip ":" */
  104. X                (void) magnitude(const_express(&a));    /* skip yemax */
  105. X            }
  106. X            if (equals(c_token, ":")) {
  107. X                c_token++;    /* skip ":" */
  108. X                (void) magnitude(const_express(&a));    /* skip wcol */
  109. X            }
  110. X            }
  111. X            if (isstring(c_token))
  112. X            c_token++;    /* skip format string */
  113. X        }
  114. X        } else {        /* function to plot */
  115. X        if (parametric)    /* working on x parametric function */
  116. X            xparam = 1 - xparam;
  117. X        dummy_func = &plot_func;
  118. X        plot_func.at = temp_at();    /* reparse function */
  119. X
  120. X        is_log_func=parametric?(xparam?is_log_x:is_log_y):is_log_y;
  121. X        for (i = 0; i < samples; i++) {
  122. X            x = x_min + i * xdiff;
  123. X            /* if (is_log_x) PEM fix logscale x axis */
  124. X            /* x = pow(base_log_x,x); 26-Sep-89 */
  125. X            (void) Gcomplex(&plot_func.dummy_values[0],
  126. X                   is_log_x ? pow(base_log_x, x) : x,
  127. X                   0.0);
  128. X
  129. X            evaluate_at(plot_func.at, &a);
  130. X
  131. X            if (undefined || (fabs(imag(&a)) > zero)) {
  132. X            this_plot->points[i].type = UNDEFINED;
  133. X            continue;
  134. X            }
  135. X            temp = real(&a);
  136. X            
  137. X            if (is_log_func && temp < 0.0) {
  138. X            this_plot->points[i].type = UNDEFINED;
  139. X            continue;
  140. X            }
  141. X            this_plot->points[i].x = x;
  142. X            this_plot->points[i].z = -1.0;  /* width of box not specified */
  143. X
  144. X            if (is_log_func) {
  145. X            if (temp == 0.0) {
  146. X                this_plot->points[i].type = OUTRANGE;
  147. X                this_plot->points[i].y = -VERYLARGE;
  148. X                continue;
  149. X            } else {
  150. X                this_plot->points[i].y = log(temp)/log_base_log_y;
  151. X            }
  152. X            } else
  153. X            this_plot->points[i].y = temp;
  154. X
  155. X            if (autoscale_ly || polar
  156. X            || inrange(temp, ymin, ymax)) {
  157. X            this_plot->points[i].type = INRANGE;
  158. X            /* When xparam is 1 we are not really computing y's! */
  159. X            if (!xparam && autoscale_ly) {
  160. X                if (temp < ymin)
  161. X                ymin = temp;
  162. X                if (temp > ymax)
  163. X                ymax = temp;
  164. X            }
  165. X            } else
  166. X            this_plot->points[i].type = OUTRANGE;
  167. X        }
  168. X        this_plot->p_count = i;    /* samples */
  169. X        }
  170. X
  171. X        /* title was handled above */
  172. X        if (almost_equals(c_token, "t$itle")) {
  173. X        c_token++;
  174. X        c_token++;
  175. X        } else if (almost_equals(c_token, "not$itle")) {
  176. X            c_token++;
  177. X        }
  178. X        /* style was handled above */
  179. X        if (almost_equals(c_token, "w$ith")) {
  180. X        c_token++;
  181. X        c_token++;
  182. X        }
  183. X        /* line and point types were handled above */
  184. X        if (!equals(c_token, ",") && !END_OF_COMMAND) {
  185. X        struct value    t;
  186. X        (void) real(const_express(&t));
  187. X        }
  188. X        if (!equals(c_token, ",") && !END_OF_COMMAND) {
  189. X        struct value    t;
  190. X        (void) real(const_express(&t));
  191. X        }
  192. X        tp_ptr = &(this_plot->next_cp);    /* used below */
  193. X        this_plot = this_plot->next_cp;
  194. X    }
  195. X
  196. X    if (equals(c_token, ","))
  197. X        c_token++;
  198. X    else
  199. X        break;
  200. X    }
  201. X
  202. X    /* throw out all curve_points at end of list, that we don't need  */
  203. X    cp_free(*tp_ptr);
  204. X    *tp_ptr = NULL;
  205. X
  206. X    /* if first_plot is NULL, we have no functions or data at all. This can
  207. X       happen, if you type "plot x=5", since x=5 is a variable assignment */
  208. X
  209. X    if(first_plot==NULL) {
  210. X    int_error("no functions or data to plot", c_token);
  211. X    }
  212. X
  213. X    if (fabs(ymax - ymin) < zero)
  214. X    /* if autoscale, widen range */
  215. X    if (autoscale_ly) {
  216. X        fprintf(stderr, "Warning: empty y range [%g:%g], ", ymin, ymax);
  217. X        if (fabs(ymin) < zero) {
  218. X        ymin = -1.;
  219. X        ymax = 1.;
  220. X        } else {
  221. X        /* expand range by 10% in either direction */
  222. X        ymin = ymin * 0.9;
  223. X        ymax = ymax * 1.1;
  224. X        }
  225. X        fprintf(stderr, "adjusting to [%g:%g]\n", ymin, ymax);
  226. X    } else {
  227. X        int_error("y range is less than `zero`", c_token);
  228. X    }
  229. X
  230. X    /* Now we finally know the real ymin and ymax */
  231. X    if (is_log_y) {
  232. X    y_min = log(ymin)/log_base_log_y;
  233. X    y_max = log(ymax)/log_base_log_y;
  234. X    } else {
  235. X    y_min = ymin;
  236. X    y_max = ymax;
  237. X    }
  238. X
  239. X    /* Set a flag so capture is not invoked  by replot itself. -hmh */
  240. X    if (plot_token != -1) {
  241. X        capture(replot_line, plot_token, c_token);
  242. X        plot_token = -1;        
  243. X    }
  244. X  
  245. X    if (parametric) {
  246. X    /* Now put t and x ranges back before we actually plot anything. */
  247. X    ltmp = autoscale_lx;
  248. X    autoscale_lx = autoscale_lt;
  249. X    autoscale_lt = ltmp;
  250. X    temp = xmin;
  251. X    xmin = tmin;
  252. X    tmin = temp;
  253. X    temp = xmax;
  254. X    xmax = tmax;
  255. X    tmax = temp;
  256. X    if (some_data_files && autoscale_lx) {
  257. X        /*
  258. X         * Stop any further autoscaling in this case (may be a mistake,
  259. X         * have to consider what is really wanted some day in the
  260. X         * future--jdc).
  261. X         */
  262. X        autoscale_lx = 0;
  263. X    }
  264. X    /* Now actually fix the plot pairs to be single plots. */
  265. X    parametric_fixup(first_plot, &plot_num, &x_min, &x_max);
  266. X    }
  267. X    if (strcmp(term_tbl[term].name, "table") == 0)
  268. X    print_table();
  269. X    else
  270. X    do_plot(first_plot, plot_num, x_min, x_max, y_min, y_max);
  271. X    cp_free(first_plot);
  272. X    first_plot = NULL;
  273. X}
  274. X
  275. Xstatic void 
  276. Xparse_title(crnt_param, start_token, end_token,
  277. X        xtitle, ytitle, this_plot, do_parse)
  278. Xint crnt_param, start_token, end_token;
  279. Xchar **xtitle, **ytitle;
  280. Xstruct surface_points *this_plot;
  281. XTBOOLEAN do_parse;
  282. X{
  283. X    static char title[256];
  284. X
  285. X    if (do_parse) {
  286. X    if (almost_equals(c_token, "t$itle")) {
  287. X        if (parametric) {
  288. X        if (crnt_param)
  289. X            int_error("\"title\" allowed only after parametric function fully specified",
  290. X                  c_token);
  291. X        else {
  292. X            /* Remove default title */
  293. X            if (*xtitle != NULL)
  294. X            (*xtitle)[0] = '\0';
  295. X            if (*ytitle != NULL)
  296. X            (*ytitle)[0] = '\0';
  297. X        }
  298. X        }
  299. X        c_token++;
  300. X        if (isstring(c_token)) {
  301. X        m_quote_capture(&(this_plot->title), c_token, c_token);
  302. X        } else {
  303. X        int_error("expecting \"title\" for plot", c_token);
  304. X        }
  305. X        c_token++;
  306. X    }  else if (almost_equals(c_token, "not$itle")) {
  307. X        c_token++;
  308. X    }  else {
  309. X        m_capture(&(this_plot->title), start_token, end_token);
  310. X        if (crnt_param == 1)
  311. X        *xtitle = this_plot->title;
  312. X        if (crnt_param == 2)
  313. X        *ytitle = this_plot->title;
  314. X    }
  315. X    }
  316. X    else {
  317. X    this_plot->title = alloc(strlen(title) + 1);
  318. X    strcpy(this_plot->title, title);
  319. X    }
  320. X}
  321. X
  322. X/*
  323. X * This parses the splot command after any range specifications. To support
  324. X * autoscaling on the x/z axis, we want any data files to define the x/y
  325. X * range, then to plot any functions using that range. We thus parse the
  326. X * input twice, once to pick up the data files, and again to pick up the
  327. X * functions. Definitions are processed twice, but that won't hurt.
  328. X */
  329. Xeval_3dplots()
  330. X{
  331. X    register int    i, j;
  332. X    register struct surface_points *this_plot=NULL, **tp_3d_ptr;
  333. X    register int    start_token, end_token;
  334. X    register int    begin_token;
  335. X    double          x_min, x_max, y_min, y_max, z_min, z_max;
  336. X    register double x, xdiff, xisodiff, y, ydiff, yisodiff, temp;
  337. X    static struct value a;
  338. X    TBOOLEAN         ltmp, some_data_files = FALSE,is_log_func = FALSE;
  339. X    int             plot_num, line_num, point_num, crnt_param = 0;    /* 0=z, 1=x, 2=y */
  340. X    char           *xtitle;
  341. X    char           *ytitle;
  342. X    void            parametric_3dfixup();
  343. X
  344. X    /* Reset first_3dplot. This is usually done at the end of this function.
  345. X       If there is an error within this function, the memory is left allocated,
  346. X       since we cannot call sp_free if the list is incomplete */
  347. X    first_3dplot=NULL;
  348. X
  349. X    if (autoscale_lz) {
  350. X    zmin = VERYLARGE;
  351. X    zmax = -VERYLARGE;
  352. X    } else if (is_log_z && (zmin <= 0.0 || zmax <= 0.0))
  353. X    int_error("z range must be above 0 for log scale!",
  354. X          NO_CARET);
  355. X
  356. X    tp_3d_ptr = &(first_3dplot);
  357. X    plot_num = 0;
  358. X    line_num = 0;        /* default line type */
  359. X    point_num = 0;        /* default point type */
  360. X
  361. X    xtitle = NULL;
  362. X    ytitle = NULL;
  363. X
  364. X    begin_token = c_token;
  365. X
  366. X    /*** First Pass: Read through data files ***/
  367. X    /*
  368. X     * This pass serves to set the x/yranges and to parse the command, as
  369. X     * well as filling in every thing except the function data. That is done
  370. X     * after the x/yrange is defined.
  371. X     */
  372. X    while (TRUE) {
  373. X    if (END_OF_COMMAND)
  374. X        int_error("function to plt3d expected", c_token);
  375. X
  376. X    start_token = c_token;
  377. X
  378. X    if (is_definition(c_token)) {
  379. X        define();
  380. X    } else {
  381. X        plot_num++;
  382. X
  383. X        if (isstring(c_token)) {    /* data file to plot */
  384. X        int line_type = line_num,
  385. X            point_type = point_num,
  386. X            plot_style = data_style,
  387. X            first_mesh = TRUE;
  388. X
  389. X        if (parametric && crnt_param != 0)
  390. X            int_error("previous parametric function not fully specified",
  391. X                  c_token);
  392. X
  393. X        if (!some_data_files) {
  394. X            if (autoscale_lx) {
  395. X            xmin = VERYLARGE;
  396. X            xmax = -VERYLARGE;
  397. X            }
  398. X            if (autoscale_ly) {
  399. X            ymin = VERYLARGE;
  400. X            ymax = -VERYLARGE;
  401. X            }
  402. X        }
  403. X        some_data_files = TRUE;
  404. X
  405. X        do {
  406. X            if (*tp_3d_ptr)
  407. X            this_plot = *tp_3d_ptr;
  408. X            else {        /* no memory malloc()'d there yet */
  409. X            /* Allocate enough isosamples and samples */
  410. X            this_plot = sp_alloc(0, 0, 0, 0);
  411. X            *tp_3d_ptr = this_plot;
  412. X            }
  413. X
  414. X            this_plot->plot_type = DATA3D;
  415. X            end_token = c_token;
  416. X            /* this also parses index/using option */
  417. X            get_3ddata(this_plot);
  418. X
  419. X            parse_title(crnt_param, start_token, end_token,
  420. X                &xtitle, &ytitle, this_plot, first_mesh);
  421. X            if (!first_mesh) plot_num++;
  422. X
  423. X            if (first_mesh) {
  424. X            if (almost_equals(c_token, "w$ith")) {
  425. X                plot_style = this_plot->plot_style = get_style();
  426. X            }
  427. X            if (!equals(c_token, ",") && !END_OF_COMMAND) {
  428. X                struct value    t;
  429. X                line_type = (int) real(const_express(&t)) - 1;
  430. X            }
  431. X            if (!equals(c_token, ",") && !END_OF_COMMAND) {
  432. X                struct value    t;
  433. X                point_type = (int) real(const_express(&t)) - 1;
  434. X            }
  435. X            first_mesh = FALSE;
  436. X            }
  437. X
  438. X            this_plot->line_type = line_type;
  439. X            this_plot->point_type = point_type;
  440. X            this_plot->plot_style = plot_style;
  441. X
  442. X            tp_3d_ptr = &(this_plot->next_sp);
  443. X        }
  444. X        while (more_data_fp);
  445. X        } else {        /* function to plot */
  446. X        if (parametric)    /* Rotate between x/y/z axes */
  447. X            crnt_param = (crnt_param + 1) % 3;
  448. X        if (*tp_3d_ptr) {
  449. X            this_plot = *tp_3d_ptr;
  450. X            if (!hidden3d)
  451. X            sp_replace(this_plot, samples_1, iso_samples_1,
  452. X                                   samples_2, iso_samples_2);
  453. X            else
  454. X            sp_replace(this_plot, iso_samples_1, 0,
  455. X                                   0, iso_samples_2);
  456. X        } else {    /* no memory malloc()'d there yet */
  457. X            /* Allocate enough isosamples and samples */
  458. X            if (!hidden3d)
  459. X            this_plot = sp_alloc(samples_1, iso_samples_1,
  460. X                                             samples_2, iso_samples_2);
  461. X            else
  462. X            this_plot = sp_alloc(iso_samples_1, 0,
  463. X                                             0, iso_samples_2);
  464. X            *tp_3d_ptr = this_plot;
  465. X        }
  466. X
  467. X        this_plot->plot_type = FUNC3D;
  468. X        this_plot->has_grid_topology = TRUE;
  469. X        this_plot->plot_style = func_style;
  470. X        dummy_func = &plot_func;
  471. X        plot_func.at = temp_at();
  472. X        /* ignore it for now */
  473. X        end_token = c_token - 1;
  474. X
  475. X        parse_title(crnt_param, start_token, end_token,
  476. X                &xtitle, &ytitle, this_plot, TRUE);
  477. X
  478. X        this_plot->line_type = line_num;
  479. X        this_plot->point_type = point_num;
  480. X
  481. X        if (almost_equals(c_token, "w$ith")) {
  482. X            this_plot->plot_style = get_style();
  483. X        }
  484. X        if (!equals(c_token, ",") && !END_OF_COMMAND) {
  485. X            struct value    t;
  486. X            this_plot->line_type = (int) real(const_express(&t)) - 1;
  487. X        }
  488. X        if (!equals(c_token, ",") && !END_OF_COMMAND) {
  489. X            struct value    t;
  490. X            this_plot->point_type = (int) real(const_express(&t)) - 1;
  491. X        }
  492. X
  493. X        tp_3d_ptr = &(this_plot->next_sp);
  494. X        }
  495. X
  496. X        if ((this_plot->plot_style == POINTSTYLE) ||
  497. X        (this_plot->plot_style == LINESPOINTS) ||
  498. X        (this_plot->plot_style == ERRORBARS))
  499. X        if (crnt_param == 0)
  500. X            point_num +=
  501. X            1 + (draw_contour != 0)
  502. X            + (hidden3d != 0);
  503. X        if (crnt_param == 0)
  504. X        line_num += 1 + (draw_contour != 0)
  505. X            + (hidden3d != 0);
  506. X    }
  507. X
  508. X    if (equals(c_token, ","))
  509. X        c_token++;
  510. X    else
  511. X        break;
  512. X    }
  513. X
  514. X    if (parametric && crnt_param != 0)
  515. X    int_error("parametric function not fully specified", NO_CARET);
  516. X
  517. X    if (parametric) {
  518. X    /*
  519. X     * Swap u/v and x/y ranges for duration of these eval_plot
  520. X     * computations.
  521. X     */
  522. X    ltmp = autoscale_lx;
  523. X    autoscale_lx = autoscale_lu;
  524. X    autoscale_lu = ltmp;
  525. X    ltmp = autoscale_ly;
  526. X    autoscale_ly = autoscale_lv;
  527. X    autoscale_lv = ltmp;
  528. X    temp = xmin;
  529. X    xmin = umin;
  530. X    umin = temp;
  531. X    temp = xmax;
  532. X    xmax = umax;
  533. X    umax = temp;
  534. X    temp = ymin;
  535. X    ymin = vmin;
  536. X    vmin = temp;
  537. X    temp = ymax;
  538. X    ymax = vmax;
  539. X    vmax = temp;
  540. X    }
  541. X    /*** Second Pass: Evaluate the functions ***/
  542. X    /*
  543. X     * Everything is defined now, except the function data. We expect no
  544. X     * syntax errors, etc, since the above parsed it all. This makes the code
  545. X     * below simpler. If autoscale_ly, the yrange may still change.
  546. X     */
  547. X    if (xmin == xmax)
  548. X    if (autoscale_lx) {
  549. X        fprintf(stderr, "Warning: empty x range [%g:%g], ",
  550. X            xmin, xmax);
  551. X        if (xmin == 0.0) {
  552. X        /* completely arbitary */
  553. X        xmin = -1.;
  554. X        xmax = 1.;
  555. X        } else {
  556. X        /* expand range by 10% in either direction */
  557. X        xmin = xmin * 0.9;
  558. X        xmax = xmax * 1.1;
  559. X        }
  560. X        fprintf(stderr, "adjusting to [%g:%g]\n", xmin, xmax);
  561. X    } else {
  562. X        int_error("x range is empty", c_token);
  563. X    }
  564. X
  565. X    if (ymin == ymax)
  566. X    if (autoscale_ly) {
  567. X        fprintf(stderr, "Warning: empty y range [%g:%g], ",
  568. X            ymin, ymax);
  569. X        if (ymin == 0.0) {
  570. X        /* completely arbitary */
  571. X        ymin = -1.;
  572. X        ymax = 1.;
  573. X        } else {
  574. X        /* expand range by 10% in either direction */
  575. X        ymin = ymin * 0.9;
  576. X        ymax = ymax * 1.1;
  577. X        }
  578. X        fprintf(stderr, "adjusting to [%g:%g]\n", ymin, ymax);
  579. X    } else {
  580. X        int_error("y range is empty", c_token);
  581. X    }
  582. X
  583. X    /* give error if xrange badly set from missing datafile error */
  584. X    if (xmin == VERYLARGE || xmax == -VERYLARGE) {
  585. X    int_error("x range is invalid", c_token);
  586. X    }
  587. X    if (is_log_x) {
  588. X    if (xmin <= 0.0 || xmax <= 0.0)
  589. X        int_error("x range must be greater than 0 for log scale!", NO_CARET);
  590. X    x_min = log(xmin)/log_base_log_x;
  591. X    x_max = log(xmax)/log_base_log_x;
  592. X    } else {
  593. X    x_min = xmin;
  594. X    x_max = xmax;
  595. X    }
  596. X
  597. X    /* give error if yrange badly set from previous error */
  598. X    if (ymin == VERYLARGE || ymax == -VERYLARGE) {
  599. X    int_error("y range is invalid", c_token);
  600. X    }
  601. X    if (is_log_y) {
  602. X    if (ymin <= 0.0 || ymax <= 0.0)
  603. X        int_error("y range must be greater than 0 for log scale!", NO_CARET);
  604. X    y_min = log(ymin)/log_base_log_y;
  605. X    y_max = log(ymax)/log_base_log_y;
  606. X    } else {
  607. X    y_min = ymin;
  608. X    y_max = ymax;
  609. X    }
  610. X
  611. X    if (samples_1 < 2 || samples_2 < 2 || iso_samples_1 < 2 || iso_samples_2 < 2)
  612. X    int_error("samples or iso_samples < 2. Must be at least 2.", NO_CARET);
  613. X
  614. X    if (this_plot && this_plot->has_grid_topology && hidden3d) {
  615. X    xdiff = (x_max - x_min) / (iso_samples_1 - 1);
  616. X    ydiff = (y_max - y_min) / (iso_samples_2 - 1);
  617. X    } else {
  618. X    xdiff = (x_max - x_min) / (samples_1 - 1);
  619. X    ydiff = (y_max - y_min) / (samples_2 - 1);
  620. X    }
  621. X    xisodiff = (x_max - x_min) / (iso_samples_1 - 1);
  622. X    yisodiff = (y_max - y_min) / (iso_samples_2 - 1);
  623. X
  624. X    this_plot = first_3dplot;
  625. X    c_token = begin_token;    /* start over */
  626. X
  627. X    /* Read through functions */
  628. X    while (TRUE) {
  629. X    if (is_definition(c_token)) {
  630. X        define();
  631. X    } else {
  632. X        if (isstring(c_token)) {    /* data file to plot */
  633. X        /* ignore this now */
  634. X        c_token++;
  635. X        if (almost_equals(c_token, "i$ndex")) {
  636. X            struct value a;
  637. X            int index;
  638. X
  639. X            c_token++;        /* skip "index" */
  640. X            index = (int) magnitude(const_express(&a));
  641. X        }
  642. X        if (almost_equals(c_token, "u$sing")) {
  643. X            c_token++;    /* skip "using" */
  644. X            if (!isstring(c_token)) {
  645. X            struct value    a;
  646. X            (void) magnitude(const_express(&a));    /* skip xcol */
  647. X            if (equals(c_token, ":")) {
  648. X                c_token++;    /* skip ":" */
  649. X                (void) magnitude(const_express(&a));    /* skip ycol */
  650. X                if (equals(c_token, ":")) {
  651. X                c_token++;    /* skip ":" */
  652. X                (void) magnitude(const_express(&a));    /* skip zcol */
  653. X                }
  654. X            }
  655. X            }
  656. X            if (isstring(c_token))
  657. X            c_token++;    /* skip format string */
  658. X        }
  659. X        } else {        /* function to plot */
  660. X        struct iso_curve *this_iso = this_plot->iso_crvs;
  661. X        struct coordinate GPHUGE *points = this_iso->points;
  662. X        int num_sam_to_use, num_iso_to_use;
  663. X
  664. X        if (parametric)
  665. X            crnt_param = (crnt_param + 1) % 3;
  666. X        dummy_func = &plot_func;
  667. X        plot_func.at = temp_at();    /* reparse function */
  668. X
  669. X        num_iso_to_use = iso_samples_2;
  670. X        if (!(this_plot->has_grid_topology && hidden3d))
  671. X            num_sam_to_use = samples_1;
  672. X        else
  673. X            num_sam_to_use = iso_samples_1;
  674. X
  675. X        is_log_func=(!parametric)||(crnt_param==0)?is_log_z:(crnt_param==1?is_log_x:is_log_y);
  676. X        for (j = 0; j < num_iso_to_use; j++) {
  677. X            y = y_min + j * yisodiff;
  678. X            /* if (is_log_y) PEM fix logscale y axis */
  679. X            /* y = pow(log_base_log_y,y); 26-Sep-89 */
  680. X            (void) Gcomplex(&plot_func.dummy_values[1],
  681. X                   is_log_y ? pow(base_log_y, y) : y,
  682. X                   0.0);
  683. X
  684. X            for (i = 0; i < num_sam_to_use; i++) {
  685. X            x = x_min + i * xdiff;
  686. X            /* if (is_log_x) PEM fix logscale x axis */
  687. X            /* x = pow(base_log_x,x); 26-Sep-89 */
  688. X            (void) Gcomplex(&plot_func.dummy_values[0],
  689. X                       is_log_x ? pow(base_log_x, x) : x,
  690. X                       0.0);
  691. X
  692. X            points[i].x = x;
  693. X            points[i].y = y;
  694. X
  695. X            evaluate_at(plot_func.at, &a);
  696. X
  697. X            if (undefined || (fabs(imag(&a)) > zero)) {
  698. X                points[i].type = UNDEFINED;
  699. X                continue;
  700. X            }
  701. X            temp = real(&a);
  702. X
  703. X            if (is_log_func && temp < 0.0) {
  704. X                points[i].type = UNDEFINED;
  705. X                continue;
  706. X            }
  707. X            if (is_log_func) {
  708. X                if (temp == 0.0) {
  709. X                points[i].type = OUTRANGE;
  710. X                points[i].z = -VERYLARGE;
  711. X                continue;
  712. X                } else {
  713. X                points[i].z = log(temp)/log_base_log_z;
  714. X                }
  715. X            } else
  716. X                points[i].z = temp;
  717. X
  718. X            if (autoscale_lz || inrange(temp, zmin, zmax)) {
  719. X                points[i].type = INRANGE;
  720. X                if (autoscale_lz) {
  721. X                if (temp < zmin)
  722. X                    zmin = temp;
  723. X                if (temp > zmax)
  724. X                    zmax = temp;
  725. X                }
  726. X            } else
  727. X                points[i].type = OUTRANGE;
  728. X            }
  729. X            this_iso->p_count = num_sam_to_use;
  730. X            this_iso = this_iso->next;
  731. X            points = this_iso? this_iso->points: NULL;
  732. X        }
  733. X
  734. X        if (!(this_plot->has_grid_topology && hidden3d)) {
  735. X            num_iso_to_use = iso_samples_1;
  736. X            num_sam_to_use = samples_2;
  737. X            is_log_func=(!parametric)||(crnt_param==0)?is_log_z:(crnt_param==1?is_log_x:is_log_y);
  738. X            for (i = 0; i < num_iso_to_use; i++) {
  739. X            x = x_min + i * xisodiff;
  740. X            /* if (is_log_x) PEM fix logscale x axis */
  741. X            /* x = pow(base_log_x,x); 26-Sep-89 */
  742. X            (void) Gcomplex(&plot_func.dummy_values[0],
  743. X                       is_log_x ? pow(base_log_x, x) : x,
  744. X                       0.0);
  745. X
  746. X            for (j = 0; j < num_sam_to_use; j++) {
  747. X                y = y_min + j * ydiff;
  748. X                /* if (is_log_y) PEM fix logscale y axis */
  749. X                /* y = pow(base_log_y,y); 26-Sep-89 */
  750. X                (void) Gcomplex(&plot_func.dummy_values[1],
  751. X                       is_log_y ? pow(base_log_y, y) : y,
  752. X                       0.0);
  753. X
  754. X                points[j].x = x;
  755. X                points[j].y = y;
  756. X
  757. X                evaluate_at(plot_func.at, &a);
  758. X
  759. X                if (undefined || (fabs(imag(&a)) > zero)) {
  760. X                points[j].type = UNDEFINED;
  761. X                continue;
  762. X                }
  763. X                temp = real(&a);
  764. X
  765. X                if (is_log_func && temp < 0.0) {
  766. X                points[j].type = UNDEFINED;
  767. X                continue;
  768. X                }
  769. X                if (is_log_func) {
  770. X                if (temp == 0.0) {
  771. X                    points[j].type = OUTRANGE;
  772. X                    points[j].z = -VERYLARGE;
  773. X                    continue;
  774. X                } else {
  775. X                    points[j].z = log(temp)/log_base_log_z;
  776. X                }
  777. X                } else
  778. X                points[j].z = temp;
  779. X
  780. X                if (autoscale_lz
  781. X                || inrange(temp, zmin, zmax)) {
  782. X                points[j].type = INRANGE;
  783. X                if (autoscale_lz) {
  784. X                    if (temp < zmin)
  785. X                    zmin = temp;
  786. X                    if (temp > zmax)
  787. X                    zmax = temp;
  788. X                }
  789. X                } else
  790. X                points[j].type = OUTRANGE;
  791. X            }
  792. X            this_iso->p_count = num_sam_to_use;
  793. X            this_iso = this_iso->next;
  794. X            points = this_iso ? this_iso->points : NULL;
  795. X            }
  796. X        }
  797. X        }
  798. X
  799. X        /* title was handled above */
  800. X        if (almost_equals(c_token, "t$itle")) {
  801. X        c_token++;
  802. X        c_token++;
  803. X        } else if (almost_equals(c_token, "not$itle")) {
  804. X            c_token++;
  805. X        }
  806. X        /* style was handled above */
  807. X        if (almost_equals(c_token, "w$ith")) {
  808. X        c_token++;
  809. X        c_token++;
  810. X        }
  811. X        /* line and point types were handled above */
  812. X        if (!equals(c_token, ",") && !END_OF_COMMAND) {
  813. X        struct value    t;
  814. X        (void) real(const_express(&t));
  815. X        }
  816. X        if (!equals(c_token, ",") && !END_OF_COMMAND) {
  817. X        struct value    t;
  818. X        (void) real(const_express(&t));
  819. X        }
  820. X        this_plot = this_plot->next_sp;
  821. X    }
  822. X
  823. X    if (equals(c_token, ","))
  824. X        c_token++;
  825. X    else
  826. X        break;
  827. X    }
  828. X
  829. X    /* if first_3dplot is NULL, we have no functions or data at all. This can
  830. X       happen, if you type "splot x=5", since x=5 is a variable assignment */
  831. X
  832. X    if(first_3dplot==NULL) {
  833. X    int_error("no functions or data to plot", c_token);
  834. X    }
  835. X
  836. X    if (fabs(zmax - zmin) < zero)
  837. X    /* if autoscale, widen range */
  838. X    if (autoscale_lz) {
  839. X        fprintf(stderr, "Warning: empty z range [%g:%g], ", zmin, zmax);
  840. X        if (fabs(zmin) < zero) {
  841. X        zmin = -1.;
  842. X        zmax = 1.;
  843. X        } else {
  844. X        /* expand range by 10% in either direction */
  845. X        zmin = zmin * 0.9;
  846. X        zmax = zmax * 1.1;
  847. X        }
  848. X        fprintf(stderr, "adjusting to [%g:%g]\n", zmin, zmax);
  849. X    } else {
  850. X        int_error("z range is less than `zero`", c_token);
  851. X    }
  852. X
  853. X    /* Now we finally know the real zmin and zmax */
  854. X    if (is_log_z) {
  855. X    if (zmin <= 0.0 || zmax <= 0.0)
  856. X        int_error("z range must be greater than 0 for log scale!", NO_CARET);
  857. X    z_min = log(zmin)/log_base_log_z;
  858. X    z_max = log(zmax)/log_base_log_z;
  859. X    } else {
  860. X    z_min = zmin;
  861. X    z_max = zmax;
  862. X    }
  863. X
  864. X    /* Set a flag so capture is not invoked by replot itself. -hmh */
  865. X    if (plot_token != -1) {
  866. X    capture(replot_line, plot_token, c_token);
  867. X    plot_token = -1;
  868. X    }
  869. X
  870. X    if (parametric) {
  871. X    /* Now put u/v and x/y ranges back before we actually plot anything. */
  872. X    ltmp = autoscale_lx;
  873. X    autoscale_lx = autoscale_lu;
  874. X    autoscale_lu = ltmp;
  875. X    ltmp = autoscale_ly;
  876. X    autoscale_ly = autoscale_lv;
  877. X    autoscale_lv = ltmp;
  878. X    temp = xmin;
  879. X    xmin = umin;
  880. X    umin = temp;
  881. X    temp = xmax;
  882. X    xmax = umax;
  883. X    umax = temp;
  884. X    temp = ymin;
  885. X    ymin = vmin;
  886. X    vmin = temp;
  887. X    temp = ymax;
  888. X    ymax = vmax;
  889. X    vmax = temp;
  890. X
  891. X    /* Now actually fix the plot triplets to be single plots. */
  892. X    parametric_3dfixup(first_3dplot, &plot_num,
  893. X               &x_min, &x_max, &y_min, &y_max,
  894. X               &z_min, &z_max);
  895. X    if (is_log_x) {
  896. X        if (x_min <= 0.0 || x_max <= 0.0)
  897. X        int_error("x range must be greater than 0 for log scale!", NO_CARET);
  898. X        x_min = log(x_min)/log_base_log_x;
  899. X        x_max = log(x_max)/log_base_log_x;
  900. X    }
  901. X    if (is_log_y) {
  902. X        if (y_min <= 0.0 || y_max <= 0.0)
  903. X        int_error("y range must be greater than 0 for log scale!", NO_CARET);
  904. X        y_min = log(y_min)/log_base_log_y;
  905. X        y_max = log(y_max)/log_base_log_y;
  906. X    }
  907. X    if (is_log_z) {
  908. X        if (z_min <= 0.0 || z_max <= 0.0)
  909. X        int_error("z range must be greater than 0 for log scale!", NO_CARET);
  910. X        z_min = log(z_min)/log_base_log_z;
  911. X        z_max = log(z_max)/log_base_log_z;
  912. X    }
  913. X    }
  914. X
  915. X    /* Filter out empty meshes. */
  916. X    while (first_3dplot &&
  917. X       first_3dplot->num_iso_read == 0 &&
  918. X       first_3dplot->plot_type == DATA3D) {
  919. X    struct surface_points *plt = first_3dplot->next_sp;
  920. X
  921. X    first_3dplot->next_sp = NULL;
  922. X    sp_free(first_3dplot);
  923. X    plot_num--;
  924. X    first_3dplot = plt;
  925. X    }
  926. X    if (first_3dplot != NULL) {
  927. X    struct surface_points *plt1, *plt2;
  928. X    
  929. X    for (plt1 = first_3dplot, plt2 = plt1->next_sp; plt2 != NULL; ) {
  930. X        if (plt2->num_iso_read == 0 && plt2->plot_type == DATA3D) {
  931. X        plt2 = plt2->next_sp;
  932. X        plt1->next_sp->next_sp = NULL;
  933. X        sp_free(plt1->next_sp);
  934. X        plot_num--;
  935. X        plt1->next_sp = plt2;
  936. X        }
  937. X        else {
  938. X        plt1 = plt2;
  939. X        plt2 = plt2->next_sp;
  940. X        }
  941. X    }
  942. X    }
  943. X    if (first_3dplot == NULL)
  944. X    int_error("no data found in file", NO_CARET);
  945. X
  946. X    /* Creates contours if contours are to be plotted as well. */
  947. X    if (draw_contour) {
  948. X    for (this_plot = first_3dplot, i = 0;
  949. X         i < plot_num;
  950. X         this_plot = this_plot->next_sp, i++) {
  951. X        if (this_plot->contours) {
  952. X        struct gnuplot_contours *cntr, *cntrs = this_plot->contours;
  953. X
  954. X        while (cntrs) {
  955. X            cntr = cntrs;
  956. X            cntrs = cntrs->next;
  957. X            gpfarfree(cntr->coords);
  958. X            free(cntr);
  959. X        }
  960. X        }
  961. X        /* Make sure this one can be contoured. */
  962. X        if (!this_plot->has_grid_topology) {
  963. X        this_plot->contours = NULL;
  964. X        fprintf(stderr,"Notice: cannot contour non grid data!\n");
  965. X        /* changed from int_error by recommendation of rkc@xn.ll.mit.edu */
  966. X        }
  967. X        else if (this_plot->plot_type == DATA3D)
  968. X        this_plot->contours = contour(
  969. X                         this_plot->num_iso_read,
  970. X                         this_plot->iso_crvs,
  971. X                         contour_levels, contour_pts,
  972. X                         contour_kind, contour_order,
  973. X                         levels_kind, levels_list);
  974. X        else
  975. X        this_plot->contours = contour(iso_samples_2,
  976. X                          this_plot->iso_crvs,
  977. X                          contour_levels, contour_pts,
  978. X                          contour_kind, contour_order,
  979. X                          levels_kind, levels_list);
  980. X    }
  981. X    }
  982. X    if (strcmp(term_tbl[term].name, "table") == 0)
  983. X    print_3dtable(plot_num);
  984. X    else
  985. X    do_3dplot(first_3dplot, plot_num, x_min, x_max, y_min, y_max, z_min, z_max);
  986. X    sp_free(first_3dplot);
  987. X    first_3dplot = NULL;
  988. X}
  989. X
  990. Xdone(status)
  991. X    int             status;
  992. X{
  993. X    if (term && term_init)
  994. X    (*term_tbl[term].reset) ();
  995. X#ifdef vms
  996. X    vms_reset();
  997. X#endif
  998. X    exit(status);
  999. X}
  1000. X
  1001. Xvoid 
  1002. Xparametric_fixup(start_plot, plot_num, x_min, x_max)
  1003. X    struct curve_points *start_plot;
  1004. X    int            *plot_num;
  1005. X    double         *x_min, *x_max;
  1006. X/*
  1007. X * The hardest part of this routine is collapsing the FUNC plot types in the
  1008. X * list (which are gauranteed to occur in (x,y) pairs while preserving the
  1009. X * non-FUNC type plots intact.  This means we have to work our way through
  1010. X * various lists.  Examples (hand checked): start_plot:F1->F2->NULL ==>
  1011. X * F2->NULL start_plot:F1->F2->F3->F4->F5->F6->NULL ==> F2->F4->F6->NULL
  1012. X * start_plot:F1->F2->D1->D2->F3->F4->D3->NULL ==> F2->D1->D2->F4->D3->NULL
  1013. X * 
  1014. X * Of course, the more interesting work is to move the y values of the x
  1015. X * function to become the x values of the y function (checking the mins and
  1016. X * maxs as we go along).
  1017. X */
  1018. X{
  1019. X    struct curve_points *xp, *new_list, *yp = start_plot, *tmp, *free_list,
  1020. X                   *free_head = NULL;
  1021. X    int             i, tlen, curve;
  1022. X    char           *new_title;
  1023. X    double          lxmin, lxmax, temp;
  1024. X
  1025. X    if (autoscale_lx) {
  1026. X    lxmin = VERYLARGE;
  1027. X    lxmax = -VERYLARGE;
  1028. X    } else {
  1029. X    lxmin = xmin;
  1030. X    lxmax = xmax;
  1031. X    }
  1032. X
  1033. X    /*
  1034. X     * Ok, go through all the plots and move FUNC types together.  Note: this
  1035. X     * originally was written to look for a NULL next pointer, but gnuplot
  1036. X     * wants to be sticky in grabbing memory and the right number of items in
  1037. X     * the plot list is controlled by the plot_num variable.
  1038. X     * 
  1039. X     * Since gnuplot wants to do this sticky business, a free_list of
  1040. X     * curve_points is kept and then tagged onto the end of the plot list as
  1041. X     * this seems more in the spirit of the original memory behavior than
  1042. X     * simply freeing the memory.  I'm personally not convinced this sort of
  1043. X     * concern is worth it since the time spent computing points seems to
  1044. X     * dominate any garbage collecting that might be saved here...
  1045. X     */
  1046. X    new_list = xp = start_plot;
  1047. X    yp = xp->next_cp;
  1048. X    curve = 0;
  1049. X
  1050. X    for (; curve < *plot_num; xp = xp->next_cp, yp = yp ? yp->next_cp : yp, curve++) {
  1051. X    if (xp->plot_type != FUNC) {
  1052. X        continue;
  1053. X    }
  1054. X    /* Here's a FUNC parametric function defined as two parts. */
  1055. X    --(*plot_num);
  1056. X    /*
  1057. X     * Go through all the points assigning the y's from xp to be the x's
  1058. X     * for yp.  Check max's and min's as you go.
  1059. X     */
  1060. X    for (i = 0; i < yp->p_count; ++i) {
  1061. X        /*
  1062. X         * Throw away excess xp points, mark excess yp points as
  1063. X         * OUTRANGE.
  1064. X         */
  1065. X        if (i > xp->p_count) {
  1066. X        yp->points[i].type = OUTRANGE;
  1067. X        continue;
  1068. X        }
  1069. X        /*
  1070. X         * Just as we had to do when we computed y values--now check that
  1071. X         * x's (computed parametrically) are in the permitted ranges as
  1072. X         * well.
  1073. X         */
  1074. X        temp = xp->points[i].y;    /* New x value for yp function. */
  1075. X        yp->points[i].x = temp;
  1076. X        /* Handle undefined values differently from normal ranges. */
  1077. X        if (xp->points[i].type == UNDEFINED)
  1078. X        yp->points[i].type = xp->points[i].type;
  1079. X        if (autoscale_lx || polar
  1080. X        || inrange(temp, lxmin, lxmax)) {
  1081. X        if (autoscale_lx && temp < lxmin)
  1082. X            lxmin = temp;
  1083. X        if (autoscale_lx && temp > lxmax)
  1084. X            lxmax = temp;
  1085. X        } else
  1086. X        yp->points[i].type = OUTRANGE;    /* Due to x value. */
  1087. X    }
  1088. X    /* Ok, fix up the title to include both the xp and yp plots. */
  1089. X    if (xp->title && xp->title[0] != '\0') {
  1090. X        tlen = strlen(yp->title) + strlen(xp->title) + 3;
  1091. X        new_title = alloc((unsigned long) tlen, "string");
  1092. X        strcpy(new_title, xp->title);
  1093. X        strcat(new_title, ", ");    /* + 2 */
  1094. X        strcat(new_title, yp->title);    /* + 1 = + 3 */
  1095. X        free(yp->title);
  1096. X        yp->title = new_title;
  1097. X    }
  1098. X    /* Eliminate the first curve (xparam) and just use the second. */
  1099. X    if (xp == start_plot) {
  1100. X        /* Simply nip off the first element of the list. */
  1101. X        new_list = first_plot = yp;
  1102. X        xp = xp->next_cp;
  1103. X        if (yp->next_cp != NULL)
  1104. X        yp = yp->next_cp;
  1105. X        /* Add start_plot to the free_list. */
  1106. X        if (free_head == NULL) {
  1107. X        free_list = free_head = start_plot;
  1108. X        free_head->next_cp = NULL;
  1109. X        } else {
  1110. X        free_list->next_cp = start_plot;
  1111. X        start_plot->next_cp = NULL;
  1112. X        free_list = start_plot;
  1113. X        }
  1114. X    } else {
  1115. X        /* Here, remove the xp node and replace it with the yp node. */
  1116. X        tmp = xp;
  1117. X        /* Pass over any data files that might have been in place. */
  1118. X        while (new_list->next_cp && new_list->next_cp != xp)
  1119. X        new_list = new_list->next_cp;
  1120. X        new_list->next_cp = yp;
  1121. X        new_list = new_list->next_cp;
  1122. X        xp = xp->next_cp;
  1123. X        if (yp->next_cp != NULL)
  1124. X        yp = yp->next_cp;
  1125. X        /* Add tmp to the free_list. */
  1126. X        tmp->next_cp = NULL;
  1127. X        if (free_head == NULL) {
  1128. X        free_list = free_head = tmp;
  1129. X        } else {
  1130. X        free_list->next_cp = tmp;
  1131. X        free_list = tmp;
  1132. X        }
  1133. X    }
  1134. X    }
  1135. X    /* Ok, stick the free list at the end of the curve_points plot list. */
  1136. X    while (new_list->next_cp != NULL)
  1137. X    new_list = new_list->next_cp;
  1138. X    new_list->next_cp = free_head;
  1139. X
  1140. X    /* Report the overall graph mins and maxs. */
  1141. X    *x_min = lxmin;
  1142. X    *x_max = lxmax;
  1143. X}
  1144. X
  1145. Xvoid 
  1146. Xparametric_3dfixup(start_plot, plot_num, x_min, x_max, y_min, y_max,
  1147. X           z_min, z_max)
  1148. X    struct surface_points *start_plot;
  1149. X    int            *plot_num;
  1150. X    double         *x_min, *x_max, *y_min, *y_max, *z_min, *z_max;
  1151. X/*
  1152. X * The hardest part of this routine is collapsing the FUNC plot types in the
  1153. X * list (which are gauranteed to occur in (x,y,z) triplets while preserving
  1154. X * the non-FUNC type plots intact.  This means we have to work our way
  1155. X * through various lists.  Examples (hand checked):
  1156. X * start_plot:F1->F2->F3->NULL ==> F3->NULL
  1157. X * start_plot:F1->F2->F3->F4->F5->F6->NULL ==> F3->F6->NULL
  1158. X * start_plot:F1->F2->F3->D1->D2->F4->F5->F6->D3->NULL ==>
  1159. X * F3->D1->D2->F6->D3->NULL
  1160. X */
  1161. X{
  1162. X    struct surface_points *xp, *yp, *zp, *new_list, *tmp, *free_list, *free_head = NULL;
  1163. X    struct iso_curve *icrvs, *xicrvs, *yicrvs, *zicrvs;
  1164. X    int             i, tlen, surface;
  1165. X    char           *new_title;
  1166. X    double          lxmin, lxmax, lymin, lymax, lzmin, lzmax;
  1167. X
  1168. X    if (autoscale_lx) {
  1169. X    lxmin = VERYLARGE;
  1170. X    lxmax = -VERYLARGE;
  1171. X    } else {
  1172. X    lxmin = xmin;
  1173. X    lxmax = xmax;
  1174. X    }
  1175. X
  1176. X    if (autoscale_ly) {
  1177. X    lymin = VERYLARGE;
  1178. X    lymax = -VERYLARGE;
  1179. X    } else {
  1180. X    lymin = ymin;
  1181. X    lymax = ymax;
  1182. X    }
  1183. X
  1184. X    if (autoscale_lz) {
  1185. X    lzmin = VERYLARGE;
  1186. X    lzmax = -VERYLARGE;
  1187. X    } else {
  1188. X    lzmin = zmin;
  1189. X    lzmax = zmax;
  1190. X    }
  1191. X
  1192. X    /*
  1193. X     * Ok, go through all the plots and move FUNC3D types together.  Note:
  1194. X     * this originally was written to look for a NULL next pointer, but
  1195. X     * gnuplot wants to be sticky in grabbing memory and the right number of
  1196. X     * items in the plot list is controlled by the plot_num variable.
  1197. X     * 
  1198. X     * Since gnuplot wants to do this sticky business, a free_list of
  1199. X     * surface_points is kept and then tagged onto the end of the plot list
  1200. X     * as this seems more in the spirit of the original memory behavior than
  1201. X     * simply freeing the memory.  I'm personally not convinced this sort of
  1202. X     * concern is worth it since the time spent computing points seems to
  1203. X     * dominate any garbage collecting that might be saved here...
  1204. X     */
  1205. X    new_list = xp = start_plot;
  1206. X    for (surface = 0; surface < *plot_num; surface++) {
  1207. X    if (xp->plot_type != FUNC3D) {
  1208. X        icrvs = xp->iso_crvs;
  1209. X
  1210. X        while (icrvs) {
  1211. X        struct coordinate GPHUGE *points = icrvs->points;
  1212. X
  1213. X        for (i = 0; i < icrvs->p_count; ++i) {
  1214. X            if (lxmin > points[i].x)
  1215. X            lxmin = points[i].x;
  1216. X            if (lxmax < points[i].x)
  1217. X            lxmax = points[i].x;
  1218. X            if (lymin > points[i].y)
  1219. X            lymin = points[i].y;
  1220. X            if (lymax < points[i].y)
  1221. X            lymax = points[i].y;
  1222. X            if (lzmin > points[i].z)
  1223. X            lzmin = points[i].z;
  1224. X            if (lzmax < points[i].z)
  1225. X            lzmax = points[i].z;
  1226. X        }
  1227. X
  1228. X        icrvs = icrvs->next;
  1229. X        }
  1230. X        xp = xp->next_sp;
  1231. X        continue;
  1232. X    }
  1233. X    yp = xp->next_sp;
  1234. X    zp = yp->next_sp;
  1235. X
  1236. X    /* Here's a FUNC3D parametric function defined as three parts. */
  1237. X    (*plot_num) -= 2;
  1238. X    /*
  1239. X     * Go through all the points and assign the x's and y's from xp and
  1240. X     * yp to zp.  Check max's and min's as you go.
  1241. X     */
  1242. X    xicrvs = xp->iso_crvs;
  1243. X    yicrvs = yp->iso_crvs;
  1244. X    zicrvs = zp->iso_crvs;
  1245. X    while (zicrvs) {
  1246. X        struct coordinate GPHUGE *xpoints = xicrvs->points, GPHUGE *ypoints = yicrvs->points, GPHUGE *zpoints = zicrvs->points;
  1247. X        for (i = 0; i < zicrvs->p_count; ++i) {
  1248. X        zpoints[i].x = xpoints[i].z;
  1249. X        zpoints[i].y = ypoints[i].z;
  1250. X
  1251. X        if (lxmin > zpoints[i].x)
  1252. X            lxmin = zpoints[i].x;
  1253. X        if (lxmax < zpoints[i].x)
  1254. X            lxmax = zpoints[i].x;
  1255. X        if (lymin > zpoints[i].y)
  1256. X            lymin = zpoints[i].y;
  1257. X        if (lymax < zpoints[i].y)
  1258. X            lymax = zpoints[i].y;
  1259. X        if (lzmin > zpoints[i].z)
  1260. X            lzmin = zpoints[i].z;
  1261. X        if (lzmax < zpoints[i].z)
  1262. X            lzmax = zpoints[i].z;
  1263. X        }
  1264. X        xicrvs = xicrvs->next;
  1265. X        yicrvs = yicrvs->next;
  1266. X        zicrvs = zicrvs->next;
  1267. X    }
  1268. X
  1269. X    /* Ok, fix up the title to include xp and yp plots. */
  1270. X    if ((xp->title && xp->title[0] != '\0') ||
  1271. X        (yp->title && yp->title[0] != '\0')) {
  1272. X        tlen = (xp->title ? strlen(xp->title) : 0) +
  1273. X        (yp->title ? strlen(yp->title) : 0) +
  1274. X        (zp->title ? strlen(zp->title) : 0) + 5;
  1275. X        new_title = alloc((unsigned long) tlen, "string");
  1276. X        new_title[0] = 0;
  1277. X        if (xp->title) {
  1278. X        strcat(new_title, xp->title);
  1279. X        strcat(new_title, ", ");    /* + 2 */
  1280. X        }
  1281. X        if (yp->title) {
  1282. X        strcat(new_title, yp->title);
  1283. X        strcat(new_title, ", ");    /* + 2 */
  1284. X        }
  1285. X        if (zp->title) {
  1286. X        strcat(new_title, zp->title);
  1287. X        }
  1288. X        free(zp->title);
  1289. X        zp->title = new_title;
  1290. X    }
  1291. X    /*
  1292. X     * Eliminate the first two surfaces (xp and yp) and just use the
  1293. X     * third.
  1294. X     */
  1295. X    if (xp == start_plot) {
  1296. X        /* Simply nip off the first two elements of the list. */
  1297. X        new_list = first_3dplot = zp;
  1298. X        xp = zp->next_sp;
  1299. X        /* Add xp and yp to the free_list. */
  1300. X        if (free_head == NULL) {
  1301. X        free_head = start_plot;
  1302. X        } else {
  1303. X        free_list->next_sp = start_plot;
  1304. X        }
  1305. X        free_list = start_plot->next_sp;
  1306. X        free_list->next_sp = NULL;
  1307. X    } else {
  1308. X        /*
  1309. X         * Here, remove the xp,yp nodes and replace them with the zp
  1310. X         * node.
  1311. X         */
  1312. X        tmp = xp;
  1313. X        /* Pass over any data files that might have been in place. */
  1314. X        while (new_list->next_sp && new_list->next_sp != xp)
  1315. X        new_list = new_list->next_sp;
  1316. X        new_list->next_sp = zp;
  1317. X        new_list = zp;
  1318. X        xp = zp->next_sp;
  1319. X        /* Add tmp to the free_list. */
  1320. X        if (free_head == NULL) {
  1321. X        free_head = tmp;
  1322. X        } else {
  1323. X        free_list->next_sp = tmp;
  1324. X        }
  1325. X        free_list = tmp->next_sp;
  1326. X        free_list->next_sp = NULL;
  1327. X    }
  1328. X    }
  1329. X    /* Ok, stick the free list at the end of the surface_points plot list. */
  1330. X    while (new_list->next_sp != NULL)
  1331. X    new_list = new_list->next_sp;
  1332. X    new_list->next_sp = free_head;
  1333. X    if (lxmax - lxmin < zero) {
  1334. X    if (fabs(lxmax) < zero) {
  1335. X        lxmin = -1.0;
  1336. X        lxmax = 1.0;
  1337. X    } else {
  1338. X        lxmin *= 0.9;
  1339. X        lxmax *= 1.1;
  1340. X    }
  1341. X    }
  1342. X    if (lymax - lymin < zero) {
  1343. X    if (fabs(lymax) < zero) {
  1344. X        lymin = -1.0;
  1345. X        lymax = 1.0;
  1346. X    } else {
  1347. X        lymin *= 0.9;
  1348. X        lymax *= 1.1;
  1349. X    }
  1350. X    }
  1351. X    if (lzmax - lzmin < zero) {
  1352. X    if (fabs(lzmax) < zero) {
  1353. X        lzmin = -1.0;
  1354. X        lzmax = 1.0;
  1355. X    } else {
  1356. X        lzmin *= 0.9;
  1357. X        lzmax *= 1.1;
  1358. X    }
  1359. X    }
  1360. X    /* Report the overall graph mins and maxs. */
  1361. X    if (autoscale_lx) {
  1362. X    *x_min = (is_log_x ? pow(base_log_x, lxmin) : lxmin);
  1363. X    *x_max = (is_log_x ? pow(base_log_x, lxmax) : lxmax);
  1364. X    } else {
  1365. X    *x_min = xmin;
  1366. X    *x_max = xmax;
  1367. X    }
  1368. X    if (autoscale_ly) {
  1369. X    *y_min = (is_log_y ? pow(base_log_y, lymin) : lymin);
  1370. X    *y_max = (is_log_y ? pow(base_log_y, lymax) : lymax);
  1371. X    } else {
  1372. X    *y_min = ymin;
  1373. X    *y_max = ymax;
  1374. X    }
  1375. X    if (autoscale_lz) {
  1376. X    *z_min = (is_log_z ? pow(base_log_z, lzmin) : lzmin);
  1377. X    *z_max = (is_log_z ? pow(base_log_z, lzmax) : lzmax);
  1378. X    } else {
  1379. X    *z_min = zmin;
  1380. X    *z_max = zmax;
  1381. X    }
  1382. X}
  1383. X
  1384. X#ifdef AMIGA_SC_6_1
  1385. Xvoid 
  1386. Xsleep(delay)
  1387. X    unsigned int    delay;
  1388. X{
  1389. X    Delay(50 * delay);
  1390. X}
  1391. X#endif
  1392. X
  1393. X#ifdef AMIGA_AC_5
  1394. Xvoid 
  1395. Xsleep(delay)
  1396. X    unsigned int    delay;
  1397. X{
  1398. X    unsigned long   time_is_up;
  1399. X    time_is_up = time(NULL) + (unsigned long) delay;
  1400. X    while (time(NULL) < time_is_up)
  1401. X     /* wait */ ;
  1402. X}
  1403. X#endif
  1404. X
  1405. X#if defined(MSDOS) || defined(_Windows) || defined(DOS386)
  1406. X#if (!defined(__TURBOC__) && !defined(__EMX__) && !defined(DJGPP)) || defined(_Windows) /* Turbo C already has sleep() */
  1407. X#ifndef __ZTC__            /* ZTC already has usleep() */
  1408. X/* kludge to provide sleep() for msc 5.1 */
  1409. Xvoid 
  1410. Xsleep(delay)
  1411. X    unsigned int    delay;
  1412. X{
  1413. X    unsigned long   time_is_up;
  1414. X    time_is_up = time(NULL) + (unsigned long) delay;
  1415. X    while (time(NULL) < time_is_up)
  1416. X     /* wait */ ;
  1417. X}
  1418. X#endif                /* not ZTC */
  1419. X#endif                /* (!TURBOC && !__EMX__ && !DJGPP) or _Windows */
  1420. X#endif                /* MSDOS || _Windows*/
  1421. X
  1422. X
  1423. X/* Support for input, shell, and help for various systems */
  1424. X
  1425. X#ifdef vms
  1426. X
  1427. X#include <descrip.h>
  1428. X#include <rmsdef.h>
  1429. X#include <errno.h>
  1430. X#include <smgdef.h>
  1431. X#include <smgmsg.h>
  1432. X
  1433. Xextern          lib$get_input(), lib$put_output();
  1434. Xextern          smg$read_composed_line();
  1435. X
  1436. Xint             vms_len;
  1437. X
  1438. Xunsigned int    status[2] =
  1439. X{1, 0};
  1440. X
  1441. Xstatic char     help[MAX_LINE_LEN + 1] = "gnuplot";
  1442. X
  1443. X$DESCRIPTOR(prompt_desc, PROMPT);
  1444. X$DESCRIPTOR(line_desc, input_line);
  1445. X
  1446. X$DESCRIPTOR(help_desc, help);
  1447. X$DESCRIPTOR(helpfile_desc, "GNUPLOT$HELP");
  1448. X
  1449. X
  1450. Xread_line(prompt)
  1451. X    char           *prompt;
  1452. X{
  1453. X    int             more, start = 0;
  1454. X    char            expand_prompt[40];
  1455. X
  1456. X    prompt_desc.dsc$w_length = strlen(prompt);
  1457. X    prompt_desc.dsc$a_pointer = prompt;
  1458. X    (void) strcpy(expand_prompt, "_");
  1459. X    (void) strncat(expand_prompt, prompt, 38);
  1460. X    do {
  1461. X    line_desc.dsc$w_length = MAX_LINE_LEN - start;
  1462. X    line_desc.dsc$a_pointer = &input_line[start];
  1463. X    switch (status[1] = smg$read_composed_line(&vms_vkid, 0, &line_desc, &prompt_desc, &vms_len)) {
  1464. X    case SMG$_EOF:
  1465. X        done(IO_SUCCESS);    /* ^Z isn't really an error */
  1466. X        break;
  1467. X    case RMS$_TNS:        /* didn't press return in time */
  1468. X        vms_len--;        /* skip the last character */
  1469. X        break;        /* and parse anyway */
  1470. X    case RMS$_BES:        /* Bad Escape Sequence */
  1471. X    case RMS$_PES:        /* Partial Escape Sequence */
  1472. X        sys$putmsg(status);
  1473. X        vms_len = 0;    /* ignore the line */
  1474. X        break;
  1475. X    case SS$_NORMAL:
  1476. X        break;        /* everything's fine */
  1477. X    default:
  1478. X        done(status[1]);    /* give the error message */
  1479. X    }
  1480. X    start += vms_len;
  1481. X    input_line[start] = '\0';
  1482. X    inline_num++;
  1483. X    if (input_line[start - 1] == '\\') {
  1484. X        /* Allow for a continuation line. */
  1485. X        prompt_desc.dsc$w_length = strlen(expand_prompt);
  1486. X        prompt_desc.dsc$a_pointer = expand_prompt;
  1487. X        more = 1;
  1488. X        --start;
  1489. X    } else {
  1490. X        line_desc.dsc$w_length = strlen(input_line);
  1491. X        line_desc.dsc$a_pointer = input_line;
  1492. X        more = 0;
  1493. X    }
  1494. X    } while (more);
  1495. X    return 0;
  1496. X}
  1497. X
  1498. X
  1499. Xdo_help()
  1500. X{
  1501. X    help_desc.dsc$w_length = strlen(help);
  1502. X    if ((vaxc$errno = lbr$output_help(lib$put_output, 0, &help_desc,
  1503. X               &helpfile_desc, 0, lib$get_input)) != SS$_NORMAL)
  1504. X    os_error("can't open GNUPLOT$HELP", NO_CARET);
  1505. X}
  1506. X
  1507. X
  1508. Xdo_shell()
  1509. X{
  1510. X    if ((vaxc$errno = lib$spawn()) != SS$_NORMAL) {
  1511. X    os_error("spawn error", NO_CARET);
  1512. X    }
  1513. X}
  1514. X
  1515. X
  1516. Xdo_system()
  1517. X{
  1518. X    input_line[0] = ' ';    /* an embarrassment, but... */
  1519. X
  1520. X    if ((vaxc$errno = lib$spawn(&line_desc)) != SS$_NORMAL)
  1521. X    os_error("spawn error", NO_CARET);
  1522. X
  1523. X    (void) putc('\n', stderr);
  1524. X}
  1525. X
  1526. X#else                /* vms */
  1527. X
  1528. X#ifdef _Windows
  1529. Xdo_help()
  1530. X{
  1531. X    if (END_OF_COMMAND)
  1532. X        WinHelp(textwin.hWndParent,(LPSTR)winhelpname,HELP_INDEX,(DWORD)NULL);
  1533. X    else {
  1534. X        char buf[128];
  1535. X        int start = c_token++;
  1536. X        while (!(END_OF_COMMAND))
  1537. X            c_token++;
  1538. X        capture(buf, start, c_token-1);
  1539. X        WinHelp(textwin.hWndParent,(LPSTR)winhelpname,HELP_PARTIALKEY,(DWORD)buf);
  1540. X    }
  1541. X}
  1542. X#else
  1543. X
  1544. X/*
  1545. X * do_help: (not VMS, although it would work) Give help to the user. It
  1546. X * parses the command line into helpbuf and supplies help for that string.
  1547. X * Then, if there are subtopics available for that key, it prompts the user
  1548. X * with this string. If more input is given, do_help is called recursively,
  1549. X * with the argument the index of null character in the string. Thus a more
  1550. X * specific help can be supplied. This can be done repeatedly. If null input
  1551. X * is given, the function returns, effecting a backward climb up the tree.
  1552. X * David Kotz (David.Kotz@Dartmouth.edu) 10/89
  1553. X */
  1554. X
  1555. Xdo_help()
  1556. X{
  1557. X    static char    *helpbuf = NULL;
  1558. X    static char    *prompt = NULL;
  1559. X    int             base;    /* index of first char AFTER help string */
  1560. X    int             len;    /* length of current help string */
  1561. X    TBOOLEAN         more_help;
  1562. X    TBOOLEAN         only;    /* TRUE if only printing subtopics */
  1563. X    int             subtopics;    /* 0 if no subtopics for this topic */
  1564. X    int             start;    /* starting token of help string */
  1565. X    char           *help_ptr;    /* name of help file */
  1566. X#ifdef ATARI
  1567. X    static char    help_fname[256]=""; /* keep helpfilename across calls */
  1568. X#endif
  1569. X
  1570. X    if ((help_ptr = getenv("GNUHELP")) == (char *) NULL)
  1571. X#ifndef ATARI
  1572. X    /* if can't find environment variable then just use HELPFILE */
  1573. X    help_ptr = HELPFILE;
  1574. X#else
  1575. X    /* try whether we can find the helpfile via shell_find. If not, just
  1576. X       use the default. (tnx Andreas) */
  1577. X
  1578. X#ifdef sequent
  1579. X    if( !index( HELPFILE, ':' ) && !index( HELPFILE, '/' ) &&
  1580. X        !index( HELPFILE, '\\' ) ) {
  1581. X#else
  1582. X    if( !strchr( HELPFILE, ':' ) && !strchr( HELPFILE, '/' ) &&
  1583. X        !strchr( HELPFILE, '\\' ) ) {
  1584. X#endif
  1585. X        if( strlen(help_fname)==0 ) {
  1586. X        strcpy( help_fname, HELPFILE );
  1587. X        if( shel_find( help_fname )==0 ) {
  1588. X            strcpy( help_fname, HELPFILE );
  1589. X        }
  1590. X        }
  1591. X        help_ptr=help_fname;
  1592. X    } else {
  1593. X        help_ptr=HELPFILE;
  1594. X    }
  1595. X#endif /* ATARI */
  1596. X
  1597. X    /* Since MSDOS DGROUP segment is being overflowed we can not allow such  */
  1598. X    /* huge static variables (1k each). Instead we dynamically allocate them */
  1599. X    /* on the first call to this function...                     */
  1600. X    if (helpbuf == NULL) {
  1601. X    helpbuf = alloc((unsigned long)MAX_LINE_LEN, "help buffer");
  1602. X    prompt = alloc((unsigned long)MAX_LINE_LEN, "help prompt");
  1603. X    helpbuf[0] = prompt[0] = 0;
  1604. X    }
  1605. X    len = base = strlen(helpbuf);
  1606. X
  1607. X    /* find the end of the help command */
  1608. X    for (start = c_token; !(END_OF_COMMAND); c_token++);
  1609. X    /* copy new help input into helpbuf */
  1610. X    if (len > 0)
  1611. X    helpbuf[len++] = ' ';    /* add a space */
  1612. X    capture(helpbuf + len, start, c_token - 1);
  1613. X    squash_spaces(helpbuf + base);    /* only bother with new stuff */
  1614. X    lower_case(helpbuf + base);    /* only bother with new stuff */
  1615. X    len = strlen(helpbuf);
  1616. X
  1617. X    /* now, a lone ? will print subtopics only */
  1618. X    if (strcmp(helpbuf + (base ? base + 1 : 0), "?") == 0) {
  1619. X    /* subtopics only */
  1620. X    subtopics = 1;
  1621. X    only = TRUE;
  1622. X    helpbuf[base] = '\0';    /* cut off question mark */
  1623. X    } else {
  1624. X    /* normal help request */
  1625. X    subtopics = 0;
  1626. X    only = FALSE;
  1627. X    }
  1628. X
  1629. X    switch (help(helpbuf, help_ptr, &subtopics)) {
  1630. X    case H_FOUND:{
  1631. X        /* already printed the help info */
  1632. X        /* subtopics now is true if there were any subtopics */
  1633. X        screen_ok = FALSE;
  1634. X
  1635. X        do {
  1636. X        if (subtopics && !only) {
  1637. X            /* prompt for subtopic with current help string */
  1638. X            if (len > 0)
  1639. X            (void) sprintf(prompt, "Subtopic of %s: ", helpbuf);
  1640. X            else
  1641. X            (void) strcpy(prompt, "Help topic: ");
  1642. X            read_line(prompt);
  1643. X            num_tokens = scanner(input_line);
  1644. X            c_token = 0;
  1645. X            more_help = !(END_OF_COMMAND);
  1646. X            if (more_help)
  1647. X            /* base for next level is all of current helpbuf */
  1648. X            do_help();
  1649. X        } else
  1650. X            more_help = FALSE;
  1651. X        } while (more_help);
  1652. X
  1653. X        break;
  1654. X    }
  1655. X    case H_NOTFOUND:{
  1656. X        printf("Sorry, no help for '%s'\n", helpbuf);
  1657. X        break;
  1658. X    }
  1659. X    case H_ERROR:{
  1660. X        perror(help_ptr);
  1661. X        break;
  1662. X    }
  1663. X    default:{            /* defensive programming */
  1664. X        int_error("Impossible case in switch", NO_CARET);
  1665. X        /* NOTREACHED */
  1666. X    }
  1667. X    }
  1668. X
  1669. X    helpbuf[base] = '\0';    /* cut it off where we started */
  1670. X}
  1671. X#endif  /* _Windows */
  1672. X
  1673. X#ifdef AMIGA_AC_5
  1674. Xchar            strg0[256];
  1675. X#endif
  1676. X
  1677. Xdo_system()
  1678. X{
  1679. X#ifdef AMIGA_AC_5
  1680. X    char           *parms[80];
  1681. X    void            getparms();
  1682. X
  1683. X    getparms(input_line + 1, parms);
  1684. X    if (fexecv(parms[0], parms) < 0)
  1685. X#else
  1686. X#if defined(ATARI)&&defined(__GNUC__)
  1687. X    /* use preloaded shell, if available */
  1688. X    short           (*shell_p) (char *command);
  1689. X    void           *ssp;
  1690. X
  1691. X    ssp = (void *) Super(NULL);
  1692. X    shell_p = *(short (**) (char *)) 0x4f6;
  1693. X    Super(ssp);
  1694. X
  1695. X    /* this is a bit strange, but we have to have a single if */
  1696. X    if ((shell_p ? (*shell_p) (input_line + 1) : system(input_line + 1)))
  1697. X#else
  1698. X#ifdef _Windows
  1699. X    if (winsystem(input_line + 1))
  1700. X#else
  1701. X    if (system(input_line + 1))
  1702. X#endif
  1703. X#endif
  1704. X#endif
  1705. X    os_error("system() failed", NO_CARET);
  1706. X}
  1707. X
  1708. X#ifdef AMIGA_AC_5
  1709. X
  1710. X/******************************************************************************/
  1711. X/* */
  1712. X/* Parses the command string (for fexecv use) and  converts the first token  */
  1713. X/* to lower case                                                          */
  1714. X/* */
  1715. X/******************************************************************************/
  1716. X
  1717. Xvoid 
  1718. Xgetparms(command, parms)
  1719. X    char           *command;
  1720. X    char          **parms;
  1721. X{
  1722. X    register int    i = 0;    /* A bunch of indices          */
  1723. X    register int    j = 0;
  1724. X    register int    k = 0;
  1725. X
  1726. X    while (*(command + j) != '\0') {    /* Loop on string characters   */
  1727. X    parms[k++] = strg0 + i;
  1728. X    while (*(command + j) == ' ')
  1729. X        ++j;
  1730. X    while (*(command + j) != ' ' && *(command + j) != '\0') {
  1731. X        if (*(command + j) == '"')    /* Get quoted string           */
  1732. X        for (*(strg0 + (i++)) = *(command + (j++));
  1733. X             *(command + j) != '"';
  1734. X             *(strg0 + (i++)) = *(command + (j++)));
  1735. X        *(strg0 + (i++)) = *(command + (j++));
  1736. X    }
  1737. X    *(strg0 + (i++)) = '\0';/* NUL terminate every token   */
  1738. X    }
  1739. X    parms[k] = '\0';
  1740. X
  1741. X    for (k = strlen(strg0) - 1; k >= 0; --k)    /* Convert to lower case       */
  1742. X    *(strg0 + k) >= 'A' && *(strg0 + k) <= 'Z' ? *(strg0 + k) |= 32 : *(strg0 + k);
  1743. X}
  1744. X
  1745. X#endif                /* AMIGA_AC_5 */
  1746. X
  1747. X#ifdef READLINE
  1748. Xchar           *
  1749. Xrlgets(s, n, prompt)
  1750. X    char           *s;
  1751. X    int             n;
  1752. X    char           *prompt;
  1753. X{
  1754. X    char           *readline();
  1755. X    static char    *line = (char *) NULL;
  1756. X
  1757. X    /* If we already have a line, first free it */
  1758. X    if (line != (char *) NULL)
  1759. X    free(line);
  1760. X
  1761. X    line = readline((interactive) ? prompt : "");
  1762. X
  1763. X    /* If it's not an EOF */
  1764. X    if (line) {
  1765. X    if (*line)
  1766. X        add_history(line);
  1767. X    strncpy(s, line, n);
  1768. X    return s;
  1769. X    }
  1770. X    return line;
  1771. X}
  1772. X#endif                /* READLINE */
  1773. X
  1774. X#if defined(MSDOS) || defined(_Windows) || defined(DOS386)
  1775. X
  1776. X#ifndef _Windows
  1777. X#ifdef __TURBOC__ 
  1778. X/* cgets implemented using dos functions */
  1779. X/* Maurice Castro 22/5/91 */
  1780. Xchar           *
  1781. Xdoscgets(s)
  1782. X    char           *s;
  1783. X{
  1784. X    long            datseg;
  1785. X
  1786. X    /* protect and preserve segments - call dos to do the dirty work */
  1787. X    datseg = _DS;
  1788. X
  1789. X    _DX = FP_OFF(s);
  1790. X    _DS = FP_SEG(s);
  1791. X    _AH = 0x0A;
  1792. X    geninterrupt(33);
  1793. X    _DS = datseg;
  1794. X
  1795. X    /* check for a carriage return and then clobber it with a null */
  1796. X    if (s[s[1] + 2] == '\r')
  1797. X    s[s[1] + 2] = 0;
  1798. X
  1799. X    /* return the input string */
  1800. X    return (&(s[2]));
  1801. X}
  1802. X#endif                /* __TURBOC__ */
  1803. X#endif                /* !_Windows */
  1804. X
  1805. X#ifdef __ZTC__
  1806. Xvoid cputs(char *s)
  1807. X{
  1808. X   register int i = 0;
  1809. X   while (s[i] != '\0')  bdos(0x02, s[i++], NULL);
  1810. X}
  1811. Xchar *cgets(char *s)
  1812. X{
  1813. X   bdosx(0x0A, s, NULL);
  1814. X
  1815. X   if (s[s[1]+2] == '\r')
  1816. X      s[s[1]+2] = 0;
  1817. X
  1818. X   /* return the input string */
  1819. X   return(&(s[2]));
  1820. X}
  1821. X#endif   /* __ZTC__ */
  1822. X
  1823. X
  1824. Xread_line(prompt)
  1825. X    char           *prompt;
  1826. X{
  1827. X    register int    i;
  1828. X    int             start = 0, ilen = 0;
  1829. X    TBOOLEAN         more;
  1830. X    int             last;
  1831. X    char           *p, *crnt_prompt = prompt;
  1832. X
  1833. X    if (interactive) {        /* if interactive use console IO so CED will
  1834. X                 * work */
  1835. X#ifndef READLINE
  1836. X#if defined(_Windows) || defined(__EMX__) || defined(DJGPP) || defined(__ZTC__)
  1837. X    printf("%s", prompt);
  1838. X#else
  1839. X    cputs(prompt);
  1840. X#endif
  1841. X#endif                /* READLINE */
  1842. X    do {
  1843. X        ilen = MAX_LINE_LEN - start - 1;
  1844. X        input_line[start] = ilen > 126 ? 126 : ilen;
  1845. X#ifdef READLINE
  1846. X        input_line[start + 2] = 0;
  1847. X        (void) rlgets(&(input_line[start + 2]), ilen, crnt_prompt);
  1848. X#ifdef sequent
  1849. X        if ((p = index(&(input_line[start + 2]), '\r'))!=NULL)
  1850. X        *p = 0;
  1851. X        if ((p = index(&(input_line[start + 2]), '\n'))!=NULL)
  1852. X        *p = 0;
  1853. X#else
  1854. X        if ((p = strchr(&(input_line[start + 2]), '\r'))!=NULL)
  1855. X        *p = 0;
  1856. X        if ((p = strchr(&(input_line[start + 2]), '\n'))!=NULL)
  1857. X        *p = 0;
  1858. X#endif
  1859. X        input_line[start + 1] = strlen(&(input_line[start + 2]));
  1860. X#else                /* READLINE */
  1861. X#if defined(_Windows) || defined(__EMX__) || defined(DJGPP)
  1862. X        (void) gets(&(input_line[start+2]));
  1863. X#else
  1864. X#ifdef __TURBOC__
  1865. X        (void) doscgets(&(input_line[start]));
  1866. X#else                /* __TURBOC__ */
  1867. X        (void) cgets(&(input_line[start]));
  1868. X#endif                /* __TURBOC__ */
  1869. X#endif                /* _Windows || __EMX__ || DJGPP*/
  1870. X        (void) putc('\n', stderr);
  1871. X#endif                /* READLINE */
  1872. X        if (input_line[start + 2] == 26) {
  1873. X        /* end-of-file */
  1874. X        (void) putc('\n', stderr);
  1875. X        input_line[start] = '\0';
  1876. X        inline_num++;
  1877. X        if (start > 0)    /* don't quit yet - process what we have */
  1878. X            more = FALSE;
  1879. X        else {
  1880. X            (void) putc('\n', stderr);
  1881. X            return(1); /* exit gnuplot */
  1882. X            /* NOTREACHED */
  1883. X        }
  1884. X        } else {
  1885. X        /* normal line input */
  1886. X        register        i = start;
  1887. X        while ((input_line[i] = input_line[i + 2]) != (char) NULL)
  1888. X            i++;    /* yuck!  move everything down two characters */
  1889. X
  1890. X        inline_num++;
  1891. X        last = strlen(input_line) - 1;
  1892. X        if (last<0) last=0;  /* stop UAE in Windows */
  1893. X        if (last + 1 >= MAX_LINE_LEN)
  1894. X            int_error("Input line too long", NO_CARET);
  1895. X
  1896. X        if (input_line[last] == '\\') {    /* line continuation */
  1897. X            start = last;
  1898. X            more = TRUE;
  1899. X        } else
  1900. X            more = FALSE;
  1901. X        }
  1902. X#ifndef READLINE
  1903. X        if (more)
  1904. X#if defined(_Windows) || defined(__EMX__) || defined(DJGPP) || defined(__ZTC__)
  1905. X        printf("> ");
  1906. X#else
  1907. X        cputs("> ");
  1908. X#endif
  1909. X#else
  1910. X        crnt_prompt = "> ";
  1911. X#endif                /* READLINE */
  1912. X    } while (more);
  1913. X    } else {            /* not interactive */
  1914. X    if (interactive)
  1915. X        fputs(prompt, stderr);
  1916. X    do {
  1917. X        /* grab some input */
  1918. X        if (fgets(&(input_line[start]), MAX_LINE_LEN - start, stdin)
  1919. X        == (char *) NULL) {
  1920. X        /* end-of-file */
  1921. X        if (interactive)
  1922. X            (void) putc('\n', stderr);
  1923. X        input_line[start] = '\0';
  1924. X        inline_num++;
  1925. X        if (start > 0)    /* don't quit yet - process what we have */
  1926. X            more = FALSE;
  1927. X        else
  1928. X            return(1);  /* exit gnuplot */
  1929. X        } else {
  1930. X        /* normal line input */
  1931. X        last = strlen(input_line) - 1;
  1932. X        if (input_line[last] == '\n') {    /* remove any newline */
  1933. X            input_line[last] = '\0';
  1934. X            /* Watch out that we don't backup beyond 0 (1-1-1) */
  1935. X            if (last > 0)
  1936. X            --last;
  1937. X            inline_num++;
  1938. X        } else if (last + 1 >= MAX_LINE_LEN)
  1939. X            int_error("Input line too long", NO_CARET);
  1940. X
  1941. X        if (input_line[last] == '\\') {    /* line continuation */
  1942. X            start = last;
  1943. X            more = TRUE;
  1944. X        } else
  1945. X            more = FALSE;
  1946. X        }
  1947. X        if (more && interactive)
  1948. X        fputs("> ", stderr);
  1949. X    } while (more);
  1950. X    }
  1951. X    return(0);
  1952. X}
  1953. X
  1954. X
  1955. Xdo_shell()
  1956. X{
  1957. X    register char  *comspec;
  1958. X    if ((comspec = getenv("COMSPEC")) == (char *) NULL)
  1959. X    comspec = "\\command.com";
  1960. X#ifdef _Windows
  1961. X    if (WinExec(comspec, SW_SHOWNORMAL) <= 32)
  1962. X#else
  1963. X#ifdef DJGPP
  1964. X    if (system(comspec) == -1)
  1965. X#else
  1966. X    if (spawnl(P_WAIT, comspec, NULL) == -1)
  1967. X#endif
  1968. X#endif
  1969. X    os_error("unable to spawn shell", NO_CARET);
  1970. X}
  1971. X
  1972. X#else                /* MSDOS */
  1973. X/* plain old Unix */
  1974. X
  1975. Xread_line(prompt)
  1976. X    char           *prompt;
  1977. X{
  1978. X    int             start = 0;
  1979. X    TBOOLEAN         more = FALSE;
  1980. X    int             last = 0;
  1981. X
  1982. X#ifndef READLINE
  1983. X    if (interactive)
  1984. X    fputs(prompt, stderr);
  1985. X#endif                /* READLINE */
  1986. X    do {
  1987. X    /* grab some input */
  1988. X#ifdef READLINE
  1989. X    if (((interactive)
  1990. X         ? rlgets(&(input_line[start]), MAX_LINE_LEN - start,
  1991. X              ((more) ? "> " : prompt))
  1992. X         : fgets(&(input_line[start]), MAX_LINE_LEN - start, stdin))
  1993. X        == (char *) NULL) {
  1994. X#else
  1995. X    if (fgets(&(input_line[start]), MAX_LINE_LEN - start, stdin)
  1996. X        == (char *) NULL) {
  1997. X#endif                /* READLINE */
  1998. X        /* end-of-file */
  1999. X        if (interactive)
  2000. X        (void) putc('\n', stderr);
  2001. X        input_line[start] = '\0';
  2002. X        inline_num++;
  2003. X        if (start > 0)    /* don't quit yet - process what we have */
  2004. X        more = FALSE;
  2005. X        else
  2006. X        return(1); /* exit gnuplot */
  2007. X    } else {
  2008. X        /* normal line input */
  2009. X        last = strlen(input_line) - 1;
  2010. X        if (input_line[last] == '\n') {    /* remove any newline */
  2011. X        input_line[last] = '\0';
  2012. X        /* Watch out that we don't backup beyond 0 (1-1-1) */
  2013. X        if (last > 0)
  2014. X            --last;
  2015. X        } else if (last + 1 >= MAX_LINE_LEN)
  2016. X        int_error("Input line too long", NO_CARET);
  2017. X
  2018. X        if (input_line[last] == '\\') {    /* line continuation */
  2019. X        start = last;
  2020. X        more = TRUE;
  2021. X        } else
  2022. X        more = FALSE;
  2023. X    }
  2024. X#ifndef READLINE
  2025. X    if (more && interactive)
  2026. X        fputs("> ", stderr);
  2027. X#endif
  2028. X    } while (more);
  2029. X    return(0);
  2030. X}
  2031. X
  2032. X#ifdef VFORK
  2033. X
  2034. Xdo_shell()
  2035. X{
  2036. X    register char  *shell;
  2037. X    register int    p;
  2038. X    static int      execstat;
  2039. X    if (!(shell = getenv("SHELL")))
  2040. X    shell = SHELL;
  2041. X#ifdef AMIGA_AC_5
  2042. X    execstat = fexecl(shell, shell, NULL);
  2043. X#else
  2044. X    if ((p = vfork()) == 0) {
  2045. X    execstat = execl(shell, shell, NULL);
  2046. X    _exit(1);
  2047. X    } else if (p == -1)
  2048. X    os_error("vfork failed", c_token);
  2049. X    else
  2050. X    while (wait(NULL) != p)
  2051. X#endif
  2052. X        ;
  2053. X    if (execstat == -1)
  2054. X    os_error("shell exec failed", c_token);
  2055. X    (void) putc('\n', stderr);
  2056. X}
  2057. X#else                /* VFORK */
  2058. X
  2059. X#ifdef AMIGA_SC_6_1
  2060. Xdo_shell()
  2061. X{
  2062. X    register char  *shell;
  2063. X    if (!(shell = getenv("SHELL")))
  2064. X    shell = SHELL;
  2065. X
  2066. X    if (system(shell))
  2067. X    os_error("system() failed", NO_CARET);
  2068. X
  2069. X    (void) putc('\n', stderr);
  2070. X}
  2071. X#else                /* AMIGA_SC_6_1 */
  2072. X#ifdef OS2
  2073. Xdo_shell()
  2074. X{
  2075. X    register char  *shell;
  2076. X    if (!(shell = getenv("COMSPEC")))
  2077. X    shell = SHELL;
  2078. X
  2079. X    if (system(shell) == -1 )
  2080. X    os_error("system() failed", NO_CARET);
  2081. X
  2082. X    (void) putc('\n', stderr);
  2083. X}
  2084. X#else                           /* ! OS2 */
  2085. X#define EXEC "exec "
  2086. Xdo_shell()
  2087. X{
  2088. X    static char     exec[100] = EXEC;
  2089. X    register char  *shell;
  2090. X    if (!(shell = getenv("SHELL")))
  2091. X    shell = SHELL;
  2092. X
  2093. X    if (system(strncpy(&exec[sizeof(EXEC) - 1], shell,
  2094. X               sizeof(exec) - sizeof(EXEC) - 1)))
  2095. X    os_error("system() failed", NO_CARET);
  2096. X
  2097. X    (void) putc('\n', stderr);
  2098. X}
  2099. X#endif                          /* OS2 */
  2100. X#endif                /* AMIGA_SC_6_1 */
  2101. X#endif                /* VFORK */
  2102. X#endif                /* MSDOS */
  2103. X#endif                /* vms */
  2104. X
  2105. X#ifdef _Windows
  2106. X/* there is a system like call on MS Windows but it is a bit difficult to 
  2107. X   use, so we will invoke the command interpreter and use it to execute the 
  2108. X   commands */
  2109. Xint winsystem(s)
  2110. Xchar *s;
  2111. X{
  2112. X    LPSTR comspec;
  2113. X    LPSTR execstr;
  2114. X    LPSTR p;
  2115. X
  2116. X    /* get COMSPEC environment variable */
  2117. X#ifdef WIN32
  2118. X    char envbuf[81];
  2119. X    GetEnvironmentVariable("COMSPEC", envbuf, 80);
  2120. X    if (*envbuf == '\0')
  2121. X        comspec = "\\command.com";
  2122. X    else
  2123. X        comspec = envbuf;
  2124. X#else
  2125. X    p = GetDOSEnvironment();
  2126. X    comspec = "\\command.com";
  2127. X    while (*p) {
  2128. X        if (!strncmp(p,"COMSPEC=",8)) {
  2129. X            comspec=p+8;
  2130. X            break;
  2131. X        }
  2132. X        p+=strlen(p)+1;
  2133. X    }
  2134. X#endif
  2135. X    /* if the command is blank we must use command.com */
  2136. X    p = s;
  2137. X    while ((*p == ' ') || (*p == '\n') || (*p == '\r'))
  2138. X        p++;
  2139. X    if (*p == '\0')
  2140. X    {
  2141. X        WinExec(comspec, SW_SHOWNORMAL);
  2142. X        }
  2143. X    else
  2144. X    {
  2145. X        /* attempt to run the windows/dos program via windows */
  2146. X        if (WinExec(s, SW_SHOWNORMAL) <= 32)
  2147. X        {
  2148. X            /* attempt to run it as a dos program from command line */
  2149. X            execstr = (char *) malloc(strlen(s) + strlen(comspec) + 6);
  2150. X            strcpy(execstr, comspec);
  2151. X            strcat(execstr, " /c ");
  2152. X            strcat(execstr, s);
  2153. X            WinExec(execstr, SW_SHOWNORMAL);
  2154. X            free(execstr);
  2155. X            }
  2156. X        }
  2157. X
  2158. X    /* regardless of the reality return OK - the consequences of */
  2159. X    /* failure include shutting down Windows */
  2160. X    return(0);        /* success */
  2161. X    }
  2162. X#endif
  2163. END_OF_FILE
  2164.   if test 57750 -ne `wc -c <'gnuplot/command.c.B'`; then
  2165.     echo shar: \"'gnuplot/command.c.B'\" unpacked with wrong size!
  2166.   elif test -f 'gnuplot/command.c.A' ; then
  2167.     echo shar: Combining  \"'gnuplot/command.c'\" \(116680 characters\)
  2168.     cat 'gnuplot/command.c.A' 'gnuplot/command.c.B' > 'gnuplot/command.c'
  2169.     if test 116680 -ne `wc -c <'gnuplot/command.c'`; then
  2170.       echo shar: \"'gnuplot/command.c'\" combined with wrong size!
  2171.     else
  2172.       rm gnuplot/command.c.A gnuplot/command.c.B
  2173.     fi
  2174.   fi
  2175.   # end of 'gnuplot/command.c.B'
  2176. fi
  2177. if test -f 'gnuplot/help.c' -a "${1}" != "-c" ; then 
  2178.   echo shar: Will not clobber existing file \"'gnuplot/help.c'\"
  2179. else
  2180.   echo shar: Extracting \"'gnuplot/help.c'\" \(19348 characters\)
  2181.   sed "s/^X//" >'gnuplot/help.c' <<'END_OF_FILE'
  2182. X#ifndef lint
  2183. Xstatic char *RCSid = "$Id: help.c%v 3.50.1.16 1993/08/27 05:04:42 woo Exp $";
  2184. X#endif
  2185. X
  2186. X
  2187. X/* GNUPLOT - help.c */
  2188. X/*
  2189. X * Copyright (C) 1986 - 1993   Thomas Williams, Colin Kelley
  2190. X *
  2191. X * Permission to use, copy, and distribute this software and its
  2192. X * documentation for any purpose with or without fee is hereby granted, 
  2193. X * provided that the above copyright notice appear in all copies and 
  2194. X * that both that copyright notice and this permission notice appear 
  2195. X * in supporting documentation.
  2196. X *
  2197. X * Permission to modify the software is granted, but not the right to
  2198. X * distribute the modified code.  Modifications are to be distributed 
  2199. X * as patches to released version.
  2200. X *  
  2201. X * This software is provided "as is" without express or implied warranty.
  2202. X * 
  2203. X *
  2204. X * AUTHORS
  2205. X * 
  2206. X *   Original Software:
  2207. X *     Thomas Williams,  Colin Kelley.
  2208. X * 
  2209. X *   Gnuplot 2.0 additions:
  2210. X *       Russell Lang, Dave Kotz, John Campbell.
  2211. X *
  2212. X *   Gnuplot 3.0 additions:
  2213. X *       Gershon Elber and many others.
  2214. X * 
  2215. X * There is a mailing list for gnuplot users. Note, however, that the
  2216. X * newsgroup 
  2217. X *     comp.graphics.gnuplot 
  2218. X * is identical to the mailing list (they
  2219. X * both carry the same set of messages). We prefer that you read the
  2220. X * messages through that newsgroup, to subscribing to the mailing list.
  2221. X * (If you can read that newsgroup, and are already on the mailing list,
  2222. X * please send a message info-gnuplot-request@dartmouth.edu, asking to be
  2223. X * removed from the mailing list.)
  2224. X *
  2225. X * The address for mailing to list members is
  2226. X *       info-gnuplot@dartmouth.edu
  2227. X * and for mailing administrative requests is 
  2228. X *       info-gnuplot-request@dartmouth.edu
  2229. X * The mailing list for bug reports is 
  2230. X *       bug-gnuplot@dartmouth.edu
  2231. X * The list of those interested in beta-test versions is
  2232. X *       info-gnuplot-beta@dartmouth.edu
  2233. X */
  2234. X
  2235. X#include <stdio.h>
  2236. X#if defined (ATARI)
  2237. X#include "plot.h"
  2238. X#else
  2239. X
  2240. Xextern int strcmp();
  2241. Xextern int      strlen();
  2242. Xextern char *strcpy();
  2243. Xextern char *strncpy();
  2244. Xextern char *strcat();
  2245. Xextern char *strncat();
  2246. Xextern char *getenv();
  2247. Xextern FILE *fopen();
  2248. Xextern char *malloc();
  2249. X#endif
  2250. X
  2251. X#include <errno.h>
  2252. Xextern int errno;
  2253. X
  2254. Xextern int instring();
  2255. X
  2256. X#define    SAME    0    /* for strcmp() */
  2257. X
  2258. X#include "help.h"    /* values passed back */
  2259. X
  2260. X#if defined(__EMX__) || defined(DJGPP) || defined(DOS386)
  2261. X#ifdef MSDOS
  2262. X#undef MSDOS    /* we have plenty of memory under __EMX__ or DJGPP */
  2263. X#endif
  2264. X#ifdef unix
  2265. X#undef unix    /* we are not unix */
  2266. X#endif
  2267. X#endif
  2268. X
  2269. X#ifdef OS2
  2270. X  /* GCC defines unix, but no PAGER, so... */
  2271. X#ifdef unix
  2272. X#undef unix
  2273. X#endif
  2274. X#endif  /* OS2 */
  2275. X
  2276. X/* help -- help subsystem that understands defined keywords
  2277. X**
  2278. X** Looks for the desired keyword in the help file at runtime, so you
  2279. X** can give extra help or supply local customizations by merely editing
  2280. X** the help file.
  2281. X**
  2282. X** The original (single-file) idea and algorithm is by John D. Johnson,
  2283. X** Hewlett-Packard Company.  Thanx and a tip of the Hatlo hat!
  2284. X**
  2285. X** Much extension by David Kotz for use in gnutex, and then in gnuplot.
  2286. X** Added output paging support, both unix and builtin. Rewrote completely
  2287. X** to read helpfile into memory, avoiding reread of help file. 12/89.
  2288. X**
  2289. X** Modified by Russell Lang to avoid reading completely into memory
  2290. X** if MSDOS defined.  This uses much less memory.  6/91
  2291. X**
  2292. X** The help file looks like this (the question marks are really in column 1):
  2293. X**
  2294. X**     ?topic
  2295. X**     This line is printed when the user wants help on "topic".
  2296. X**     ?keyword
  2297. X**     ?Keyword
  2298. X**     ?KEYWORD
  2299. X**     These lines will be printed on the screen if the user wanted
  2300. X**     help on "keyword", "Keyword", or "KEYWORD".  No casefolding is
  2301. X**    done on the keywords.
  2302. X**     ?subject
  2303. X**     ?alias
  2304. X**     This line is printed for help on "subject" and "alias".
  2305. X**     ?
  2306. X**    ??
  2307. X**     Since there is a null keyword for this line, this section
  2308. X**     is printed when the user wants general help (when a help
  2309. X**     keyword isn't given).  A command summary is usually here.
  2310. X**    Notice that the null keyword is equivalent to a "?" keyword
  2311. X**    here, because of the '?' and '??' topic lines above.
  2312. X**   If multiple keywords are given, the first is considered the 
  2313. X**   'primary' keyword. This affects a listing of available topics.
  2314. X**     ?last-subject
  2315. X**     Note that help sections are terminated by the start of the next
  2316. X**     '?' entry or by EOF.  So you can't have a leading '?' on a line
  2317. X**     of any help section.  You can re-define the magic character to
  2318. X**    recognize in column 1, though, if '?' is too useful.  (Try ^A.)
  2319. X*/
  2320. X
  2321. X#define    KEYFLAG    '?'    /* leading char in help file topic lines */
  2322. X
  2323. X/*
  2324. X** Calling sequence:
  2325. X**    int result;        # 0 == success
  2326. X**    char *keyword;        # topic to give help on
  2327. X**    char *pathname;        # path of help file
  2328. X**      int subtopics;        # set to TRUE if only subtopics to be listed
  2329. X**                # returns TRUE if subtopics were found
  2330. X**    result = help(keyword, pathname, &subtopics);
  2331. X** Sample:
  2332. X**    cmd = "search\n";
  2333. X**    helpfile = "/usr/local/lib/program/program.help";
  2334. X**    subtopics = FALSE;
  2335. X**    if (help(cmd, helpfile, &subtopics) != H_FOUND)
  2336. X**        printf("Sorry, no help for %s", cmd);
  2337. X**
  2338. X**
  2339. X** Speed this up by replacing the stdio calls with open/close/read/write.
  2340. X*/
  2341. X#ifdef    WDLEN
  2342. X#  define    PATHSIZE    WDLEN
  2343. X#else
  2344. X#  define    PATHSIZE    BUFSIZ
  2345. X#endif
  2346. X
  2347. Xtypedef int boolean;
  2348. X#ifndef TRUE
  2349. X#define TRUE (1)
  2350. X#define FALSE (0)
  2351. X#endif
  2352. X
  2353. Xtypedef struct line_s LINEBUF;
  2354. Xstruct line_s {
  2355. X    char *line;            /* the text of this line */
  2356. X    LINEBUF *next;            /* the next line */
  2357. X};
  2358. X
  2359. Xtypedef struct linkey_s LINKEY;
  2360. Xstruct linkey_s {
  2361. X    char *key;                /* the name of this key */
  2362. X    long pos;                /* ftell position */
  2363. X    LINEBUF *text;            /* the text for this key */
  2364. X    boolean primary;        /* TRUE -> is a primary name for a text block */
  2365. X    LINKEY *next;            /* the next key in linked list */
  2366. X};
  2367. X
  2368. Xtypedef struct key_s KEY;
  2369. Xstruct key_s {
  2370. X    char *key;                /* the name of this key */
  2371. X    long pos;                /* ftell position */
  2372. X    LINEBUF *text;            /* the text for this key */
  2373. X    boolean primary;        /* TRUE -> is a primary name for a text block */
  2374. X};
  2375. Xstatic LINKEY *keylist = NULL;    /* linked list of keys */
  2376. Xstatic KEY *keys = NULL;        /* array of keys */
  2377. Xstatic int keycount = 0;        /* number of keys */
  2378. Xstatic FILE *helpfp = NULL;
  2379. X
  2380. Xstatic int LoadHelp();
  2381. Xstatic void sortkeys();
  2382. Xstatic int keycomp();
  2383. Xstatic LINEBUF *storeline();
  2384. Xstatic LINKEY *storekey();
  2385. Xstatic KEY *FindHelp();
  2386. Xstatic boolean Ambiguous();
  2387. X
  2388. X/* Help output */
  2389. Xstatic void PrintHelp();
  2390. Xstatic void ShowSubtopics();
  2391. Xstatic void StartOutput();
  2392. Xstatic void OutLine();
  2393. Xstatic void EndOutput();
  2394. Xstatic FILE *outfile;        /* for unix pager, if any */
  2395. Xstatic int pagelines;        /* count for builtin pager */
  2396. X#define SCREENSIZE 24        /* lines on screen (most have at least 24) */
  2397. X
  2398. X/* help:
  2399. X * print a help message 
  2400. X * also print available subtopics, if subtopics is TRUE
  2401. X */
  2402. Xhelp(keyword, path, subtopics)
  2403. X    char *keyword;        /* on this topic */
  2404. X    char *path;            /* from this file */
  2405. X    boolean *subtopics;    /* (in) - subtopics only? */
  2406. X                        /* (out) - are there subtopics? */
  2407. X{
  2408. X    static char oldpath[PATHSIZE] = "";    /* previous help file */
  2409. X    int status;            /* result of LoadHelp */
  2410. X    KEY *key;            /* key that matches keyword */
  2411. X
  2412. X    /*
  2413. X    ** Load the help file if necessary (say, first time we enter this routine,
  2414. X    ** or if the help file changes from the last time we were called).
  2415. X    ** Also may occur if in-memory copy was freed.
  2416. X    ** Calling routine may access errno to determine cause of H_ERROR.
  2417. X    */
  2418. X    errno = 0;
  2419. X    if (strncmp(oldpath, path, PATHSIZE) != SAME)
  2420. X     FreeHelp();
  2421. X    if (keys == NULL) {
  2422. X       status = LoadHelp(path);
  2423. X       if (status == H_ERROR)
  2424. X        return(status);
  2425. X
  2426. X       /* save the new path in oldpath */
  2427. X       if (strlen(path) < PATHSIZE)
  2428. X        (void) strcpy(oldpath, path);
  2429. X       else {                /* not enough room in oldpath, sigh */
  2430. X          (void) strncpy(oldpath, path, PATHSIZE - 1);
  2431. X          oldpath[PATHSIZE - 1] = '\0';
  2432. X       }
  2433. X    }
  2434. X
  2435. X    /* look for the keyword in the help file */
  2436. X    key = FindHelp(keyword);
  2437. X    if (key != NULL) {
  2438. X       /* found the keyword: print help and return */
  2439. X       PrintHelp(key, subtopics);
  2440. X       status = H_FOUND;
  2441. X    } else {
  2442. X       status = H_NOTFOUND;
  2443. X    }
  2444. X
  2445. X    return(status);
  2446. X}
  2447. X
  2448. X/* we only read the file once, into memory
  2449. X * except for MSDOS when we don't read all the file -
  2450. X * just the keys and location of the text
  2451. X */
  2452. Xstatic int
  2453. XLoadHelp(path)
  2454. X    char *path;
  2455. X{
  2456. X    LINKEY *key;            /* this key */
  2457. X    long pos;                /* ftell location within help file */
  2458. X    char buf[BUFSIZ];        /* line from help file */
  2459. X    LINEBUF *head;            /* head of text list  */
  2460. X    LINEBUF *firsthead = NULL;
  2461. X    boolean primary;        /* first ? line of a set is primary */
  2462. X    boolean flag;
  2463. X
  2464. X    if ((helpfp = fopen(path, "r")) == NULL) {
  2465. X       /* can't open help file, so error exit */
  2466. X       return (H_ERROR);
  2467. X    }
  2468. X
  2469. X    /*
  2470. X    ** The help file is open.  Look in there for the keyword.
  2471. X    */
  2472. X    (void) fgets(buf, BUFSIZ - 1, helpfp);
  2473. X    while (!feof(helpfp)) {
  2474. X       /*
  2475. X        ** Make an entry for each synonym keyword
  2476. X        */
  2477. X       primary = TRUE;
  2478. X       while (buf[0] == KEYFLAG) {
  2479. X          key = storekey(buf+1);    /* store this key */
  2480. X          key->primary = primary;
  2481. X          key->text = NULL;            /* fill in with real value later */
  2482. X          key->pos = 0;                /* fill in with real value later */
  2483. X          primary = FALSE;
  2484. X          pos = ftell(helpfp);
  2485. X          if (fgets(buf, BUFSIZ - 1, helpfp) == (char *)NULL)
  2486. X            break;
  2487. X       }
  2488. X       /*
  2489. X        ** Now store the text for this entry.
  2490. X        ** buf already contains the first line of text.
  2491. X        */
  2492. X#ifndef MSDOS
  2493. X       firsthead = storeline(buf);
  2494. X       head = firsthead;
  2495. X#endif
  2496. X       while ( (fgets(buf, BUFSIZ - 1, helpfp) != (char *)NULL)
  2497. X        && (buf[0] != KEYFLAG) ){
  2498. X#ifndef MSDOS
  2499. X          /* save text line */
  2500. X          head->next = storeline(buf);
  2501. X          head = head->next;
  2502. X#endif
  2503. X       }
  2504. X       /* make each synonym key point to the same text */
  2505. X       do {
  2506. X          key->pos = pos;
  2507. X          key->text = firsthead;
  2508. X          flag = key->primary;
  2509. X          key = key->next;
  2510. X       } while ( flag!=TRUE  &&  key!=NULL );
  2511. X    }
  2512. X#ifndef MSDOS
  2513. X    (void) fclose(helpfp);
  2514. X#endif
  2515. X
  2516. X    /* we sort the keys so we can use binary search later */
  2517. X    sortkeys();
  2518. X    return(H_FOUND); /* ok */
  2519. X}
  2520. X
  2521. X/* make a new line buffer and save this string there */
  2522. Xstatic LINEBUF *
  2523. Xstoreline(text)
  2524. X    char *text;
  2525. X{
  2526. X    LINEBUF *new;
  2527. X
  2528. X    new = (LINEBUF *)malloc(sizeof(LINEBUF));
  2529. X    if (new == NULL)
  2530. X     int_error("not enough memory to store help file", -1);
  2531. X    if (text != NULL) {
  2532. X       new->line = (char *) malloc((unsigned int)(strlen(text)+1));
  2533. X       if (new->line == NULL)
  2534. X        int_error("not enough memory to store help file", -1);
  2535. X       (void) strcpy(new->line, text);
  2536. X    } else
  2537. X     new->line = NULL;
  2538. X
  2539. X    new->next = NULL;
  2540. X
  2541. X    return(new);
  2542. X}
  2543. X
  2544. X/* Add this keyword to the keys list, with the given text */
  2545. Xstatic LINKEY *
  2546. Xstorekey(key)
  2547. X    char *key;
  2548. X{
  2549. X    LINKEY *new;
  2550. X
  2551. X    key[strlen(key)-1] = '\0'; /* cut off \n  */
  2552. X
  2553. X    new = (LINKEY *)malloc(sizeof(LINKEY));
  2554. X    if (new == NULL)
  2555. X     int_error("not enough memory to store help file", -1);
  2556. X    new->key = (char *) malloc((unsigned int)(strlen(key)+1));
  2557. X    if (new->key == NULL)
  2558. X     int_error("not enough memory to store help file", -1);
  2559. X    (void) strcpy(new->key, key);
  2560. X
  2561. X    /* add to front of list */
  2562. X    new->next = keylist;
  2563. X    keylist = new;
  2564. X    keycount++;
  2565. X    return(new);
  2566. X}
  2567. X
  2568. X/* we sort the keys so we can use binary search later */
  2569. X/* We have a linked list of keys and the number.
  2570. X * to sort them we need an array, so we reform them into an array,
  2571. X * and then throw away the list.
  2572. X */
  2573. Xstatic void
  2574. Xsortkeys()
  2575. X{
  2576. X    LINKEY *p,*n;            /* pointers to linked list */
  2577. X    int i;                /* index into key array */
  2578. X    
  2579. X    /* allocate the array */
  2580. X    keys = (KEY *)malloc((unsigned int)((keycount+1) * sizeof(KEY)));
  2581. X    if (keys == NULL)
  2582. X     int_error("not enough memory to store help file", -1);
  2583. X    
  2584. X    /* copy info from list to array, freeing list */
  2585. X    for (p = keylist, i = 0; p != NULL; p = n, i++) {
  2586. X       keys[i].key = p->key;
  2587. X       keys[i].pos = p->pos;
  2588. X       keys[i].text = p->text;
  2589. X       keys[i].primary = p->primary;
  2590. X       n = p->next;
  2591. X       free( (char *)p );
  2592. X    }
  2593. X
  2594. X    /* a null entry to terminate subtopic searches */
  2595. X    keys[keycount].key = NULL;
  2596. X    keys[keycount].pos = 0;
  2597. X    keys[keycount].text = NULL;
  2598. X
  2599. X    /* sort the array */
  2600. X    /* note that it only moves objects of size (two pointers + long + int) */
  2601. X    /* it moves no strings */
  2602. X    qsort((char *)keys, keycount, sizeof(KEY), keycomp);
  2603. X}
  2604. X
  2605. Xstatic int
  2606. Xkeycomp(a, b)
  2607. X    KEY *a,*b;
  2608. X{
  2609. X    return (strcmp(a->key, b->key));
  2610. X}
  2611. X
  2612. X/* Free the help file from memory. */
  2613. X/* May be called externally if space is needed */
  2614. Xvoid
  2615. XFreeHelp()
  2616. X{
  2617. X    int i;                /* index into keys[] */
  2618. X    LINEBUF *t, *next;
  2619. X
  2620. X    if (keys == NULL)
  2621. X     return;
  2622. X
  2623. X    for (i = 0; i < keycount; i++) {
  2624. X       free( (char *)keys[i].key );
  2625. X       if (keys[i].primary)   /* only try to release text once! */
  2626. X       for (t = keys[i].text; t != NULL; t = next) {
  2627. X          free( (char *)t->line );
  2628. X          next = t->next;
  2629. X          free( (char *)t );
  2630. X       }
  2631. X    }
  2632. X    free( (char *)keys );
  2633. X    keys = NULL;
  2634. X    keycount = 0;
  2635. X#ifdef MSDOS
  2636. X    (void) fclose(helpfp);
  2637. X#endif
  2638. X}
  2639. X
  2640. X/* FindHelp:
  2641. X *  Find the key that matches the keyword.
  2642. X *  The keys[] array is sorted by key.
  2643. X *  We could use a binary search, but a linear search will aid our
  2644. X *  attempt to allow abbreviations. We search for the first thing that
  2645. X *  matches all the text we're given. If not an exact match, then
  2646. X *  it is an abbreviated match, and there must be no other abbreviated
  2647. X *  matches -- for if there are, the abbreviation is ambiguous. 
  2648. X *  We print the ambiguous matches in that case, and return not found.
  2649. X */
  2650. Xstatic KEY *                /* NULL if not found */
  2651. XFindHelp(keyword)
  2652. X    char *keyword;            /* string we look for */
  2653. X{
  2654. X    KEY *key;
  2655. X    int len = strlen(keyword);
  2656. X    int compare;
  2657. X
  2658. X    for (key = keys, compare = 1; key->key != NULL && compare > 0; key++) {
  2659. X       compare = strncmp(keyword, key->key, len);
  2660. X       if (compare == 0)    /* we have a match! */
  2661. X        if (!Ambiguous(key, len)) {
  2662. X            /* non-ambiguous abbreviation */
  2663. X            (void) strcpy(keyword, key->key); /* give back the full spelling */
  2664. X            return(key);        /* found!! */
  2665. X        }
  2666. X    }
  2667. X
  2668. X    /* not found, or ambiguous */
  2669. X    return(NULL);
  2670. X}
  2671. X
  2672. X/* Ambiguous:
  2673. X * Check the key for ambiguity up to the given length.
  2674. X * It is ambiguous if it is not a complete string and there are other
  2675. X * keys following it with the same leading substring.
  2676. X */
  2677. Xstatic boolean
  2678. XAmbiguous(key, len)
  2679. X    KEY *key;
  2680. X    int len;
  2681. X{
  2682. X    char *first;
  2683. X    char *prev;
  2684. X    boolean status = FALSE;    /* assume not ambiguous */
  2685. X    int compare;
  2686. X    int sublen;
  2687. X
  2688. X    if (key->key[len] == '\0')
  2689. X     return(FALSE);
  2690. X    
  2691. X    for (prev = first = key->key, compare = 0, key++;
  2692. X        key->key != NULL && compare == 0; key++) {
  2693. X       compare = strncmp(first, key->key, len);
  2694. X       if (compare == 0) {
  2695. X          /* So this key matches the first one, up to len.
  2696. X           * But is it different enough from the previous one
  2697. X           * to bother printing it as a separate choice?
  2698. X           */
  2699. X          sublen = instring(prev+len, ' ');
  2700. X          if (strncmp(key->key, prev, len+sublen) != 0) {
  2701. X             /* yup, this is different up to the next space */
  2702. X             if (!status) {
  2703. X                /* first one we have printed is special */
  2704. X                fprintf(stderr, 
  2705. X                       "Ambiguous request '%.*s'; possible matches:\n",
  2706. X                       len, first);
  2707. X                fprintf(stderr, "\t%s\n", prev);
  2708. X                status = TRUE;
  2709. X             }
  2710. X             fprintf(stderr, "\t%s\n", key->key);
  2711. X             prev = key->key;
  2712. X          }
  2713. X       }
  2714. X    }
  2715. X    
  2716. X    return(status);
  2717. X}
  2718. X
  2719. X/* PrintHelp:
  2720. X * print the text for key
  2721. X */
  2722. Xstatic void
  2723. XPrintHelp(key, subtopics)
  2724. X    KEY *key;
  2725. X    boolean *subtopics;        /* (in) - subtopics only? */
  2726. X                        /* (out) - are there subtopics? */
  2727. X{
  2728. X    LINEBUF *t;
  2729. X#ifdef MSDOS
  2730. X    char buf[BUFSIZ];        /* line from help file */
  2731. X#endif
  2732. X
  2733. X    StartOutput();
  2734. X
  2735. X    if (subtopics == NULL || !*subtopics) {
  2736. X#ifdef MSDOS
  2737. X       fseek(helpfp,key->pos,0);
  2738. X       while ( (fgets(buf, BUFSIZ - 1, helpfp) != (char *)NULL)
  2739. X            && (buf[0] != KEYFLAG) ) {
  2740. X          OutLine(buf);
  2741. X       }
  2742. X#else
  2743. X       for (t = key->text; t != NULL; t = t->next)
  2744. X        OutLine(t->line);        /* print text line */
  2745. X#endif
  2746. X    }
  2747. X
  2748. X    ShowSubtopics(key, subtopics);
  2749. X    OutLine("\n");
  2750. X
  2751. X    EndOutput();
  2752. X}
  2753. X
  2754. X/* ShowSubtopics:
  2755. X *  Print a list of subtopic names
  2756. X */
  2757. X#define PER_LINE 4
  2758. X
  2759. Xstatic void
  2760. XShowSubtopics(key, subtopics)
  2761. X    KEY *key;                /* the topic */
  2762. X    boolean *subtopics;        /* (out) are there any subtopics */
  2763. X{
  2764. X    int subt = 0;            /* printed any subtopics yet? */
  2765. X    KEY *subkey;            /* subtopic key */
  2766. X    int len;                /* length of key name */
  2767. X    char line[BUFSIZ];        /* subtopic output line */
  2768. X    char *start;            /* position of subname in key name */
  2769. X    int sublen;            /* length of subname */
  2770. X    int pos;
  2771. X    char *prev = NULL;        /* the last thing we put on the list */
  2772. X
  2773. X    *line = '\0';
  2774. X    len = strlen(key->key);
  2775. X
  2776. X    for (subkey = key+1; subkey->key != NULL; subkey++) {
  2777. X       if (strncmp(subkey->key, key->key, len) == 0) {
  2778. X          /* find this subtopic name */
  2779. X          start = subkey->key + len;
  2780. X          if (len > 0)
  2781. X            if (*start == ' ')
  2782. X             start++;        /* skip space */
  2783. X            else
  2784. X             break;        /* not the same topic after all  */
  2785. X          else            /* here we are looking for main topics */
  2786. X            if (!subkey->primary)
  2787. X             continue;    /* not a main topic */
  2788. X          sublen = instring(start, ' ');
  2789. X          if (prev == NULL || strncmp(start, prev, sublen) != 0) {
  2790. X             if (subt == 0) {
  2791. X                subt++;
  2792. X                if (len)
  2793. X                  (void) sprintf(line, "\nSubtopics available for %s:\n", 
  2794. X                        key->key);
  2795. X                else
  2796. X                  (void) sprintf(line, "\nHelp topics available:\n");
  2797. X                OutLine(line);
  2798. X                *line = '\0';
  2799. X                pos = 0;
  2800. X             }
  2801. X             if (pos == PER_LINE) {
  2802. X                (void) strcat(line, "\n");
  2803. X                OutLine(line);
  2804. X                *line = '\0';
  2805. X                pos = 0;
  2806. X             }
  2807. X             /* adapted by DvdSchaaf */
  2808. X             {
  2809. X#define FIRSTCOL    6
  2810. X#define COLLENGTH    15
  2811. X                int spacelen, ispacelen;
  2812. X
  2813. X                 if( pos == 0 )
  2814. X                    spacelen = FIRSTCOL;
  2815. X                 for( ispacelen = 0;
  2816. X                    ispacelen < spacelen; ispacelen++ )
  2817. X                    (void) strcat(line, " ");
  2818. X                 /* commented out *
  2819. X                 (void) strcat(line, "\t");
  2820. X                 */
  2821. X                 (void) strncat(line, start, sublen);
  2822. X                 spacelen = COLLENGTH - sublen;
  2823. X                 if( spacelen <= 0 )
  2824. X                    spacelen = 1;
  2825. X             }
  2826. X             pos++;
  2827. X             prev = start;
  2828. X          }
  2829. X       } else {
  2830. X          /* new topic */
  2831. X          break;
  2832. X       }
  2833. X    }
  2834. X    
  2835. X    /* put out the last line */
  2836. X    if (subt > 0 && pos > 0) {
  2837. X       (void) strcat(line, "\n");
  2838. X       OutLine(line);
  2839. X    }
  2840. X    
  2841. X/*
  2842. X    if (subt == 0) {
  2843. X       OutLine("\n");
  2844. X       OutLine("No subtopics available\n");
  2845. X    }
  2846. X*/
  2847. X    
  2848. X    if (subtopics)
  2849. X     *subtopics = (subt != 0);
  2850. X}
  2851. X
  2852. X
  2853. X/* StartOutput:
  2854. X * Open a file pointer to a pipe to user's $PAGER, if there is one,
  2855. X * otherwise use our own pager.
  2856. X */
  2857. Xstatic void
  2858. XStartOutput()
  2859. X{
  2860. X#ifdef unix
  2861. X    char *pager_name = getenv("PAGER");
  2862. X    extern FILE *popen();
  2863. X
  2864. X    if (pager_name != NULL && *pager_name != '\0')
  2865. X     if ((outfile = popen(pager_name, "w")) != (FILE *)NULL)
  2866. X       return;            /* success */
  2867. X    outfile = stderr;
  2868. X    /* fall through to built-in pager */
  2869. X#endif
  2870. X
  2871. X    /* built-in pager */
  2872. X    pagelines = 0;
  2873. X}
  2874. X
  2875. X/* write a line of help output  */
  2876. X/* line should contain only one \n, at the end */
  2877. Xstatic void
  2878. XOutLine(line)
  2879. X    char *line;
  2880. X{
  2881. X    int c;                /* dummy input char */
  2882. X#ifdef unix
  2883. X    if (outfile != stderr) {
  2884. X       fputs(line, outfile);
  2885. X       return;
  2886. X    }
  2887. X#endif
  2888. X
  2889. X    /* built-in dumb pager */
  2890. X    /* leave room for prompt line */
  2891. X    if (pagelines >= SCREENSIZE - 2) {
  2892. X       fprintf(stderr,"Press return for more: ");
  2893. X       do 
  2894. X        c = getchar();
  2895. X       while (c != EOF && c != '\n');
  2896. X       pagelines = 0;
  2897. X    }
  2898. X    fputs(line, stderr);
  2899. X    pagelines++;
  2900. X}
  2901. X
  2902. Xstatic void
  2903. XEndOutput()
  2904. X{
  2905. X#ifdef unix
  2906. X    extern int pclose();
  2907. X
  2908. X    if (outfile != stderr)
  2909. X     (void) pclose(outfile);
  2910. X#endif
  2911. X}
  2912. END_OF_FILE
  2913.   if test 19348 -ne `wc -c <'gnuplot/help.c'`; then
  2914.     echo shar: \"'gnuplot/help.c'\" unpacked with wrong size!
  2915.   fi
  2916.   # end of 'gnuplot/help.c'
  2917. fi
  2918. echo shar: End of archive 6 \(of 33\).
  2919. cp /dev/null ark6isdone
  2920. MISSING=""
  2921. for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ; do
  2922.     if test ! -f ark${I}isdone ; then
  2923.     MISSING="${MISSING} ${I}"
  2924.     fi
  2925. done
  2926. if test "${MISSING}" = "" ; then
  2927.     echo You have unpacked all 33 archives.
  2928.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  2929. else
  2930.     echo You still must unpack the following archives:
  2931.     echo "        " ${MISSING}
  2932. fi
  2933. exit 0
  2934. exit 0 # Just in case...
  2935.