1 /******************************************************************************
2  * Copyright 1994-2010,2013 by Thomas E. Dickey                               *
3  * All Rights Reserved.                                                       *
4  *                                                                            *
5  * Permission to use, copy, modify, and distribute this software and its      *
6  * documentation for any purpose and without fee is hereby granted, provided  *
7  * that the above copyright notice appear in all copies and that both that    *
8  * copyright notice and this permission notice appear in supporting           *
9  * documentation, and that the name of the above listed copyright holder(s)   *
10  * not be used in advertising or publicity pertaining to distribution of the  *
11  * software without specific, written prior permission.                       *
12  *                                                                            *
13  * THE ABOVE LISTED COPYRIGHT HOLDER(S) DISCLAIM ALL WARRANTIES WITH REGARD   *
14  * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND  *
15  * FITNESS, IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE  *
16  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES          *
17  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN      *
18  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR *
19  * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.                *
20  ******************************************************************************/
21 
22 static const char Id[] = "$Id: add.c,v 1.51 2013/02/26 20:51:48 tom Exp $";
23 static const char copyrite[] = "Copyright 1994-2010,2013 by Thomas E. Dickey";
24 
25 /*
26  * Title:	add.c
27  * Author:	T.E.Dickey
28  * Created:	05 May 1986
29  * Modified:	(see CHANGES)
30  *
31  * Function:	This is a simple adding machine that uses curses to display
32  *		a column of values, operators and results.  The user can
33  *		move up and down in the column, modifying the values and
34  *		operators.
35  */
36 
37 #include <add.h>
38 #include <screen.h>
39 
40 #define	SALLOC(s) (s *)malloc(sizeof(s))
41 
42 #define	SSTACK	struct	SStack
43 SSTACK {
44     SSTACK *next;
45     FILE *sfp;
46     char **sscripts;
47 };
48 
49 static void Recompute(DATA *);
50 static void ShowRange(DATA *, DATA *);
51 static void ShowFrom(DATA *);
52 
53 /*
54  * Common data
55  */
56 static const char *top_output;
57 static DATA *all_data;		/* => beginning of data */
58 static DATA *top_data;		/* => beginning of current screen */
59 
60 static Value val_frac;		/* # of units in 'len_frac' (e.g., 100.0) */
61 static long big_long;		/* largest signed 'long' value */
62 static int interval;		/* compounding interval-divisor */
63 static int max_width;		/* maximum width of formatted number */
64 static int use_width;		/* working width of formatted number */
65 static int len_frac;		/* nominal number of digits after period */
66 static Bool show_error;		/* suppress normal reporting until GetC() */
67 static Bool show_scripts;	/* force script to be visible, for testing */
68 
69 /*
70  * Input-script control:
71  */
72 static SSTACK *sstack;		/* script-stack */
73 static FILE *scriptFP;		/* current script file-pointer */
74 static char **scriptv;		/* pointer to list of input-scripts */
75 static Bool scriptCHG;		/* set true if there's a change after scripts */
76 static Bool scriptNUM;		/* set true to 0..n for script number */
77 
78 /*
79  * Help-screen
80  */
81 static DATA *all_help;
82 static char *helpfile;
83 
84 #define CMD(op,l,u,f,s) {op, l, u, f, s}
85 
86 /*
87  * Check to see if the given character is a legal 'add' operator (excluding
88  * editing/scrolling):
89  */
90 /* *INDENT-OFF* */
91 static struct {
92     char command;
93     char repeats;
94     char toggles;
95     Bool isunary;
96     const char *explain;
97 } Commands[] = {
98     CMD(OP_ADD,  'a',	'A',	TRUE,	"add"),
99     CMD(OP_SUB,  's',	'S',	TRUE,	"subtract"),
100     CMD(OP_NEG,  'n',	'N',	FALSE,	"negate"),
101     CMD(OP_MUL,  'm',	'M',	FALSE,	"multiply"),
102     CMD(OP_DIV,  'd',	'D',	FALSE,	"divide"),
103     CMD(OP_INT,  'i',	'I',	FALSE,	"interest"),
104     CMD(OP_TAX,  't',	'T',	FALSE,	"tax"),
105     CMD(L_PAREN, EOS,	EOS,    TRUE,	"begin group"),
106     CMD(R_PAREN, EOS,	EOS,    FALSE,	"end group")
107 };
108 /* *INDENT-ON* */
109 
110 static void
failed(const char * msg)111 failed(const char *msg)
112 {
113     screen_finish();
114     perror(msg);
115     exit(EXIT_FAILURE);
116 }
117 
118 /*
119  * Normally we don't show the results of replaying a script. This makes
120  * loading scripts much faster.
121  */
122 static int
isVisible(void)123 isVisible(void)
124 {
125     return show_scripts || (scriptFP == 0);
126 }
127 
128 /*
129  * Lookup a character to see if it is a legal operator, returning nonnull
130  * in that case.
131  */
132 #define	LOOKUP(func,lookup,result) \
133 	static	int	func(int c) { \
134 			unsigned j; \
135 			for (j = 0; j < SIZEOF(Commands); j++) \
136 				if (Commands[j].lookup == c) \
137 					return result; \
138 			return 0; \
139 			}
140 
LOOKUP(isCommand,command,c)141 LOOKUP(isCommand, command, c)
142 LOOKUP(isRepeats, repeats, Commands[j].command)
143 LOOKUP(isToggles, toggles, Commands[j].command)
144 LOOKUP(isUnary, command, Commands[j].isunary)
145 
146 /*
147  * Find the end of the DATA list
148  */
149      static
150      DATA *EndOfData(void)
151 {
152     DATA *np;
153     if ((np = all_data) != 0) {
154 	while (!LastData(np))
155 	    np = np->next;
156     }
157     return np;
158 }
159 
160 /*
161  * Returns true if the DATA entry is either the 0th or 1st entry in the
162  * list.
163  */
164 static int
FirstData(DATA * np)165 FirstData(DATA * np)
166 {
167     return (np->prev == 0 || np->prev == all_data);
168 }
169 
170 /*
171  * Tests for special case of operators that cannot appear in a unary context.
172  */
173 static int
UnaryConflict(DATA * np,int chr)174 UnaryConflict(DATA * np, int chr)
175 {
176     if (isCommand(chr))
177 	return (!isUnary(chr) && (FirstData(np) || np->prev->psh));
178     return FALSE;
179 }
180 
181 /*
182  * Trim whitespace from the end of a string
183  */
184 static void
TrimString(char * src)185 TrimString(char *src)
186 {
187     char *end = src + strlen(src);
188 
189     while (end-- != src) {
190 	if (isspace(UCH(*end)))
191 	    *end = EOS;
192 	else
193 	    break;
194     }
195 }
196 
197 /*
198  * Allocate a string, trimming whitespace from the end for consistency.
199  */
200 static char *
AllocString(const char * src)201 AllocString(const char *src)
202 {
203     return strcpy(malloc((strlen(src) + 1)), src);
204 }
205 
206 /*
207  * Allocate and initialize a DATA entry
208  */
209 static DATA *
AllocData(DATA * after)210 AllocData(DATA * after)
211 {
212     DATA *np = SALLOC(DATA);
213 
214     if (np == 0)
215 	failed("AllocData");
216 
217     assert(np != 0);
218 
219     np->txt = 0;
220     np->val =
221 	np->sum =
222 	np->aux = 0.0;
223     np->cmd = EOS;
224     np->psh = FALSE;
225     np->dot = len_frac;
226     np->next =
227 	np->prev = 0;
228 
229     if (after != 0) {
230 	np->prev = after;
231 	np->next = after->next;
232 	after->next = np;
233 	if (!LastData(np))
234 	    np->next->prev = np;
235     } else {			/* append to the end of the list */
236 	DATA *op = EndOfData();
237 	if (op != 0) {
238 	    op->next = np;
239 	    np->prev = op;
240 	} else {
241 	    all_data = np;
242 	}
243     }
244     return np;
245 }
246 
247 /*
248  * Free and delink the data from the list.  If it was a permanent entry,
249  * recompute the display from that point.  Otherwise, simply repaint.
250  */
251 static DATA *
FreeData(DATA * np,int permanent)252 FreeData(DATA * np, int permanent)
253 {
254     DATA *prev = np->prev;
255     DATA *next = np->next;
256 
257     if (prev == 0) {		/* we're at the beginning of the list */
258 	all_data = next;
259 	if (next != 0)
260 	    next->prev = 0;
261     } else {
262 	prev->next = next;
263 	if (next != 0) {
264 	    next->prev = prev;
265 	} else {		/* deleted the end-of-data */
266 	    next = prev;
267 	}
268     }
269     if (np->txt != 0)
270 	free(np->txt);
271     free((char *) np);
272 
273     if (top_data == np)
274 	top_data = next;
275 
276     if (screen_active) {
277 	if (permanent) {
278 	    Recompute(next);
279 	} else {
280 	    ShowFrom(next);
281 	}
282     }
283     return next;
284 }
285 
286 /*
287  * Count the data from the beginning to the specified entry.
288  */
289 static int
CountData(DATA * np)290 CountData(DATA * np)
291 {
292     int seq = 0;
293     while (np != 0 && np->prev != 0) {
294 	seq++;
295 	np = np->prev;
296     }
297     return seq;
298 }
299 
300 static int
CountFromTop(DATA * np)301 CountFromTop(DATA * np)
302 {
303     return CountData(np) - CountData(top_data);
304 }
305 
306 static int
CountAllData(void)307 CountAllData(void)
308 {
309     return CountData(EndOfData());
310 }
311 
312 static DATA *
FindData(int seq)313 FindData(int seq)
314 {
315     DATA *np = all_data;
316     while (seq-- > 0) {
317 	if (np == 0)
318 	    break;
319 	np = np->next;
320     }
321     return np;
322 }
323 
324 /*
325  * Remove any fractional portion of the given value 'val', return the result.
326  */
327 static Value
Floor(Value val)328 Floor(Value val)
329 {
330     if (val > big_long) {
331 	val = (Value) big_long;
332     } else if (val < -big_long) {
333 	val = (Value) (-big_long);
334     } else {
335 	long tmp = (long) val;
336 	val = (Value) tmp;
337     }
338     return (val);
339 }
340 
341 static Value
Ceiling(Value val)342 Ceiling(Value val)
343 {
344     Value tmp = Floor(val);
345 
346     if (val > 0.0 && val > tmp) {
347 	tmp += 1.0;
348     } else if (val < 0.0 && val < tmp) {
349 	tmp -= 1.0;
350     }
351     return (tmp);
352 }
353 
354 /*
355  * Return the last operand for the given operator, excluding the given data.
356  */
357 static Value
LastVAL(const DATA * np,int cmd)358 LastVAL(const DATA * np, int cmd)
359 {
360     np = np->prev;
361     while (np != 0) {
362 	if (cmd == np->cmd)
363 	    return (np->val);
364 	np = np->prev;
365     }
366     if (cmd == OP_MUL || cmd == OP_DIV)
367 	return (1.0 * val_frac);
368     else if (cmd == OP_INT || cmd == OP_TAX)
369 	return (4.0 * val_frac);
370     return (0.0);
371 }
372 
373 /*
374  * Convert the given value 'val' to printing format (comma between groups of
375  * three digits, decimal point before fractional part).
376  */
377 static char *
Format(char * dst,Value val)378 Format(char *dst, Value val)
379 {
380     int len, j, neg = val < 0.0;
381     size_t grp;
382     char bfr[MAXBFR], *s = dst;
383 
384     if (neg) {
385 	val = -val;
386 	*s++ = OP_SUB;
387     }
388 
389     if (val >= big_long) {
390 	(void) strcpy(s, " ** overflow");
391     } else {
392 	(void) sprintf(bfr, "%0*.0f", len_frac, val);
393 	len = (int) strlen(bfr) - len_frac;
394 	grp = (size_t) (len % 3);
395 	j = 0;
396 
397 	while (j < len) {
398 	    if (grp) {
399 		(void) strncpy(s, &bfr[j], grp);
400 		j += (int) grp;
401 		s += grp;
402 		if (j < len)
403 		    *s++ = COMMA;
404 	    }
405 	    grp = 3;
406 	}
407 	(void) sprintf(s, ".%s", &bfr[len]);
408     }
409     return (dst);
410 }
411 
412 /*
413  * Convert a value to printing form, writing it on the screen:
414  */
415 static void
putval(Value val)416 putval(Value val)
417 {
418     char bfr[MAXBFR];
419 
420     screen_printf("%*.*s", use_width, use_width, Format(bfr, val));
421 }
422 
423 /*
424  */
425 static void
setval(DATA * np,int cmd,Value val,int psh)426 setval(DATA * np, int cmd, Value val, int psh)
427 {
428     np->cmd = (char) (isCommand(cmd) ? cmd : OP_ADD);
429     np->val = val;
430     np->psh = psh;
431 }
432 
433 /*
434  * Compute the parenthesis level of the given data entry.  This is a positive
435  * number.
436  */
437 static int
LevelOf(const DATA * target)438 LevelOf(const DATA * target)
439 {
440     DATA *np;
441     int level = 0;
442 
443     for (np = all_data; np != 0 && np != target; np = np->next) {
444 	if (np->cmd == R_PAREN)
445 	    level--;
446 	if (np->psh)
447 	    level++;
448     }
449     return level;
450 }
451 
452 /*
453  * Return a pointer to the last data entry in the screen.
454  */
455 static DATA *
ScreenBottom(void)456 ScreenBottom(void)
457 {
458     DATA *np = top_data;
459     int count = screen_full;
460 
461     while (--count > 0) {
462 	if (!LastData(np))
463 	    np = np->next;
464 	else
465 	    break;
466     }
467     return np;
468 }
469 
470 /*
471  * For an entry that isn't the currently-edited one, show the operator,
472  * operand and result(s).  Return the index of this entry in the screen
473  * to use in testing for completion of repainting activity.
474  */
475 static int
ShowValue(DATA * np,int * editing,Bool comment)476 ShowValue(DATA * np, int *editing, Bool comment)
477 {
478     int row = CountFromTop(np);
479     int col = 0;
480     int level;
481 
482     if (!isVisible()) {
483 	if (editing != 0) {
484 	    editing[0] =
485 		editing[1] = 0;
486 	}
487     } else if (row >= 0 && row < screen_full) {
488 	char cmd = (char) (isprint(UCH(np->cmd)) ? np->cmd : '?');
489 
490 	screen_set_position(row + 1, col);
491 	screen_clear_endline();
492 	if (np->cmd != EOS) {
493 	    for (level = LevelOf(np); level > 0; level--)
494 		screen_puts(". ");
495 	    if (editing != 0) {
496 		screen_set_reverse(TRUE);
497 		screen_printf(" %c>>", cmd);
498 		screen_set_reverse(FALSE);
499 	    } else {
500 		screen_printf(" %c: ", cmd);
501 	    }
502 
503 	    if (editing != 0) {
504 		*editing = screen_col();
505 		screen_set_reverse(TRUE);
506 	    }
507 
508 	    if ((cmd == R_PAREN) || ((editing != 0) && !comment))
509 		screen_printf("%*.*s", use_width, use_width, " ");
510 	    else if (np->psh)
511 		screen_putc(L_PAREN);
512 	    else
513 		putval(np->val);
514 
515 	    if (editing != 0)
516 		screen_set_reverse(FALSE);
517 
518 	    if (!np->psh) {
519 		screen_puts(" ");
520 		if (editing != 0)
521 		    screen_set_bold(TRUE);
522 		putval(np->sum);
523 		if (editing != 0)
524 		    screen_set_bold(FALSE);
525 	    }
526 	    if (cmd == OP_INT || cmd == OP_TAX) {
527 		screen_puts(" ");
528 		putval(np->aux);
529 	    }
530 
531 	    col = screen_col();
532 	    if (editing != 0)
533 		editing[1] = col;
534 	    col += 3;
535 	    if ((np->txt != 0) && screen_cols_left(col) > 3)
536 		screen_puts(" # ");
537 	}
538 
539 	if ((np->txt != 0)
540 	    && screen_cols_left(col) > 0) {
541 	    screen_printf("%.*s", screen_cols_left(col), np->txt);
542 	}
543 	if (LastData(np) && screen_rows_left(row) > 1) {
544 	    screen_set_position(row + 2, 0);
545 	    screen_clear_bottom();
546 	}
547     }
548     return row;
549 }
550 
551 static void
ShowRange(DATA * first,DATA * last)552 ShowRange(DATA * first, DATA * last)
553 {
554     DATA *np = first;
555     while (np != last) {
556 	if (ShowValue(np, (int *) 0, FALSE) >= screen_full)
557 	    break;
558 	np = np->next;
559     }
560 }
561 
562 static void
ShowFrom(DATA * first)563 ShowFrom(DATA * first)
564 {
565     ShowRange(first, (DATA *) 0);
566 }
567 
568 /*
569  * (Re)display the status line at the top of the screen.
570  */
571 static void
ShowStatus(DATA * np,int opened)572 ShowStatus(DATA * np, int opened)
573 {
574     int seq = CountData(np);
575     int top = CountData(top_data);
576     DATA *last = EndOfData();
577     unsigned j;
578     int c;
579     char buffer[BUFSIZ];
580 
581     if (!show_error && np != 0 && isVisible()) {
582 	screen_set_bold(TRUE);
583 	screen_set_position(0, 0);
584 	screen_clear_endline();
585 	(void) sprintf(buffer, "%d of %d", seq, CountData(last));
586 	screen_set_position(0, screen_cols_left((int) strlen(buffer)));
587 	screen_puts(buffer);
588 	screen_set_position(0, 0);
589 	if (opened < 0) {
590 	    screen_puts("Edit comment (press return to exit)");
591 	} else if (opened > 0) {
592 	    screen_puts("Open-line expecting operator ");
593 	    for (j = 0; j < SIZEOF(Commands); j++) {
594 		c = Commands[j].command;
595 		if (!isUnary(c) && FirstData(np))
596 		    continue;
597 		if ((c = Commands[j].command) == L_PAREN)
598 		    continue;
599 		if ((c == R_PAREN) && (opened < 2))
600 		    continue;
601 		screen_putc(c);
602 	    }
603 	    screen_puts(" or oO to cancel");
604 	} else if (np->cmd != EOS) {	/* editing a value */
605 	    for (j = 0; j < SIZEOF(Commands); j++) {
606 		if (Commands[j].command == np->cmd) {
607 		    screen_printf("  %s", Commands[j].explain);
608 		    break;
609 		}
610 	    }
611 	    screen_set_position(0, 5 + use_width);
612 	    putval(last->sum);
613 	    screen_puts(" -- total");
614 	}
615 	screen_set_bold(FALSE);
616     }
617     if (isVisible())
618 	screen_set_position(seq - top + 1, 0);
619 }
620 
621 /*
622  * Show text in the status line
623  */
624 static void
ShowInfo(const char * msg)625 ShowInfo(const char *msg)
626 {
627     if (screen_active) {	/* we've started curses */
628 	screen_message("%s", msg);
629     } else {
630 	(void) printf("%s\n", msg);
631     }
632 }
633 
634 /*
635  * Show an error-message in the status line
636  */
637 static void
ShowError(const char * msg,const char * arg)638 ShowError(const char *msg, const char *arg)
639 {
640     static const char format[] = "?? %s \"%s\"";
641 
642     if (screen_active) {	/* we've started curses */
643 	screen_message(format, msg, arg);
644 	show_error = TRUE;
645     } else {
646 	(void) fprintf(stderr, format, msg, arg);
647 	(void) fprintf(stderr, "\n");
648 	failed("add");
649     }
650 }
651 
652 /*
653  * Returns true if a file exists, -true if it isn't a file, false if neither.
654  */
655 static int
Fexists(const char * path)656 Fexists(const char *path)
657 {
658     struct stat sb;
659     if (*path == EOS)
660 	ShowError("No filename specified", path);
661     if (stat(path, &sb) < 0)
662 	return FALSE;
663     if ((sb.st_mode & S_IFMT) != S_IFREG) {
664 	ShowError("Not a file", path);
665 	return -TRUE;
666     }
667     return TRUE;
668 }
669 
670 /*
671  * Check file-access for writing a script
672  */
673 static int
Ok2Write(const char * path)674 Ok2Write(const char *path)
675 {
676     if (Fexists(path) != -TRUE
677 	&& access(path, 02) != 0
678 	&& errno != ENOENT) {
679 	ShowError("No write access", path);
680     } else {
681 	return TRUE;
682     }
683     return FALSE;
684 }
685 
686 /*
687  * Write the current list of data as an ADD-script
688  */
689 static void
PutScript(const char * path)690 PutScript(const char *path)
691 {
692     DATA *np;
693     FILE *fp = (path && *path) ? fopen(path, "w") : 0;
694     char buffer[MAXBFR];
695     int count = 0;
696 
697     if (fp == 0) {
698 	ShowError("Cannot open output", path);
699 	return;
700     }
701 
702     (void) sprintf(buffer, "Writing results to \"%s\"", path);
703     ShowInfo(buffer);
704 
705     for (np = all_data->next; np != 0; np = np->next) {
706 	if (np->cmd == EOS && np->next == 0)
707 	    break;
708 	(void) fprintf(fp, "%c", np->cmd);
709 	if (np->psh)
710 	    (void) fprintf(fp, "%c", L_PAREN);
711 	else if (np->cmd != R_PAREN)
712 	    (void) fprintf(fp, "%s", Format(buffer, np->val));
713 	if (!np->psh)
714 	    (void) fprintf(fp, "\t%s", Format(buffer, np->sum));
715 	if (np->cmd == OP_INT
716 	    || np->cmd == OP_TAX)
717 	    (void) fprintf(fp, "\t%s", Format(buffer, np->aux));
718 	if (np->txt != 0)
719 	    (void) fprintf(fp, "\t#%s", np->txt);
720 	(void) fprintf(fp, "\n");
721 	count++;
722     }
723     (void) fclose(fp);
724 
725     /* If we've written the specified output, reset the changed-flag */
726     if (!strcmp(path, top_output))
727 	scriptCHG = FALSE;
728 
729     (void) sprintf(buffer, "Wrote %d line%s to \"%s\"",
730 		   count, count != 1 ? "s" : "", path);
731     ShowInfo(buffer);
732     show_error++;		/* force the message to stay until next char */
733 }
734 
735 /*
736  * Check file-access for reading a script.
737  */
738 static int
Ok2Read(const char * path)739 Ok2Read(const char *path)
740 {
741     if (Fexists(path) != TRUE || access(path, 04) != 0)
742 	ShowError("No read access", path);
743     else
744 	return TRUE;
745     return FALSE;
746 }
747 
748 /*
749  * Save the current script-state and nest a new one.
750  */
751 static void
PushScripts(const char * script)752 PushScripts(const char *script)
753 {
754     SSTACK *p = SALLOC(SSTACK);
755 
756     if (p == 0)
757 	failed("PushScripts");
758 
759     assert(p != 0);
760 
761     p->next = sstack;
762     p->sfp = scriptFP;
763     p->sscripts = scriptv;
764     sstack = p;
765 
766     scriptFP = 0;
767     scriptv = (char **) calloc(2, sizeof(char *));
768     scriptv[0] = AllocString(script);
769 }
770 
771 /*
772  * Restore a previous script-state, if any.
773  */
774 static int
PopScripts(void)775 PopScripts(void)
776 {
777     SSTACK *p;
778 
779     scriptFP = 0;
780     scriptv = 0;
781     if ((p = sstack) != 0) {
782 	scriptFP = p->sfp;
783 	scriptv = p->sscripts;
784 	sstack = p->next;
785     }
786     return (scriptFP != 0);
787 }
788 
789 /*
790  * On end-of-file, go to the next script (or resume the parent script)
791  */
792 static void
NextScript(void)793 NextScript(void)
794 {
795     (void) fclose(scriptFP);
796     scriptFP = 0;
797     if (!*(++scriptv))
798 	PopScripts();
799 }
800 
801 /*
802  * Read from a script, checking for end-of-file, and performing control-char
803  * conversion.
804  */
805 static int
ReadScript(void)806 ReadScript(void)
807 {
808     int c = fgetc(scriptFP);
809     if (feof(scriptFP) || ferror(scriptFP)) {
810 	NextScript();
811 	if (!scriptNUM++)
812 	    scriptCHG = FALSE;
813 	c = EOF;
814     } else if (c == '^') {
815 	c = ReadScript();
816 	if (c == EOF)
817 	    c = '^';		/* we'll get an EOF on the next call */
818 	else if (c == '?')
819 	    c = '\177';		/* delete */
820 	else
821 	    c &= 037;
822     }
823     return c;
824 }
825 
826 /*
827  * As long as there is another input-script to process, read it.  Scripts are
828  * formatted
829  *	<operator><value><tab><ignored>
830  * to permit line-oriented entries.
831  */
832 static int
GetScript(void)833 GetScript(void)
834 {
835     static int first;
836     static int valued;
837     static int ignored;
838     static int comment;
839     int c;
840 
841     while (scriptv != 0 && *scriptv != 0) {
842 	int was_invisible = !isVisible();
843 
844 	if (scriptFP == 0) {
845 	    scriptFP = fopen(*scriptv, "r");
846 	    if (scriptFP == 0) {
847 		ShowError("Cannot read", *scriptv);
848 		scriptv++;
849 	    } else {
850 		ShowInfo("Reading script");
851 		first = TRUE;
852 		valued = FALSE;
853 	    }
854 	    continue;
855 	}
856 	while (scriptFP != 0) {
857 	    if ((c = ReadScript()) == EOF)
858 		continue;
859 	    if (c == '#' || c == COLON) {
860 		comment = TRUE;
861 		ignored = FALSE;
862 	    } else if (!comment) {
863 		/*
864 		 * We pay attention only to the first character or number on a
865 		 * given input line.
866 		 */
867 		if (c == '\t') {
868 		    ignored = TRUE;
869 		} else if (valued && c == ' ') {
870 		    ignored = TRUE;
871 		}
872 	    }
873 	    if (isReturn(c)) {
874 		first = TRUE;
875 		valued = FALSE;
876 	    }
877 	    if (ignored && isReturn(c)) {
878 		ignored = FALSE;
879 	    } else if (!ignored) {
880 		if (isReturn(c)) {
881 		    comment = FALSE;
882 		} else if (first) {
883 		    if (isdigit(UCH(c))) {
884 			ungetc(c, scriptFP);
885 			c = OP_ADD;
886 		    }
887 		    first = FALSE;
888 		}
889 		if (isdigit(UCH(c)) || (c) == '.') {
890 		    valued = TRUE;
891 		}
892 		return (c);
893 	    }
894 	}
895 	/* Finally, paint the screen if I wasn't doing so before */
896 	if (was_invisible) {
897 	    int editcols[3];
898 	    DATA *last = EndOfData();
899 	    ShowStatus(last, FALSE);
900 	    ShowRange(top_data, last);
901 	    ShowValue(last, editcols, FALSE);
902 	    first = TRUE;
903 	    valued = FALSE;
904 	    return EQUALS;	/* flush out the last line */
905 	}
906     }
907 
908     valued = FALSE;
909     comment = FALSE;
910     ignored = FALSE;
911 
912     if (first) {
913 	if (scriptNUM == 1)
914 	    scriptCHG = FALSE;
915 	first = FALSE;
916     }
917     return EOS;
918 }
919 
920 /*
921  * Read a character from an input-script, if it is available.  Otherwise, read
922  * directly from the terminal.
923  */
924 static int
GetC(void)925 GetC(void)
926 {
927     int c;
928 
929     if ((c = GetScript()) == EOS) {
930 	show_error = FALSE;
931 	c = screen_getc();
932     }
933     return (c);
934 }
935 
936 /*
937  * Given a string, offset into it and insert-position, delete the character at
938  * that offset, both from the string and screen.  Return the resulting offset.
939  */
940 static int
DeleteChar(char * buffer,int offset,int pos,int limit)941 DeleteChar(char *buffer, int offset, int pos, int limit)
942 {
943     int y, x, col;
944     char *t;
945 
946     /* delete from the actual buffer */
947     for (t = buffer + offset; (t[0] = t[1]) != EOS; t++) ;
948 
949     if (isVisible()) {		/* update the display */
950 	y = screen_row();
951 	x = screen_col();	/* get current insert-position */
952 	col = x - pos + offset;	/* assume pos < len, offset < len */
953 	screen_set_position(y, col);
954 	screen_delete_char();
955 	if (limit > 0 && (int) strlen(buffer) < limit) {
956 	    screen_set_position(y, col - offset);
957 	    screen_insert_char(' ');
958 	    x++;
959 	}
960 	if (pos > offset)
961 	    x--;
962 	screen_set_position(y, x);
963     }
964     if (pos > offset)
965 	pos--;
966     return pos;
967 }
968 
969 /*
970  * Insert a character into the given string, returning the updated insert
971  * position.  If the "rmargin" parameter is nonzero, we keep the buffer
972  * right-justified to that limit.
973  */
974 static int
InsertChar(char * buffer,int chr,int pos,int lmargin,int rmargin,int * offset)975 InsertChar(char *buffer, int chr, int pos, int lmargin, int rmargin, int *offset)
976 {
977     int y, x;
978     int len = (int) strlen(buffer);
979     char *t;
980 
981     /* perform the actual insertion into the buffer */
982     for (t = buffer + len;; t--) {
983 	t[1] = t[0];
984 	if (t == buffer + pos)
985 	    break;
986     }
987     t[0] = (char) chr;
988 
989     if (isVisible()) {		/* update the display on the screen */
990 	y = screen_row();
991 	x = screen_col();
992 	if (screen_cols_left(x) > 0) {
993 	    if (rmargin > 0) {
994 		x--;
995 		screen_set_position(y, x - pos);
996 		screen_delete_char();
997 		screen_set_position(y, x);
998 	    }
999 	    screen_insert_char(chr);
1000 	    screen_set_position(y, x + 1);
1001 	} else if (offset != 0) {
1002 	    screen_set_position(y, lmargin);
1003 	    screen_delete_char();
1004 	    screen_set_position(y, x - 1);
1005 	    screen_insert_char(chr);
1006 	    screen_set_position(y, x);
1007 	    *offset += 1;
1008 	} else {
1009 	    screen_alarm();
1010 	}
1011     }
1012     return pos + 1;
1013 }
1014 
1015 /*
1016  * Delete from the buffer the character to the left of the given col-position.
1017  */
1018 static int
doDeleteChar(char * buffer,int col,int limit)1019 doDeleteChar(char *buffer, int col, int limit)
1020 {
1021     if (col > 0) {
1022 	col = DeleteChar(buffer, col - 1, col, limit);
1023     } else {
1024 	screen_alarm();
1025     }
1026     return col;
1027 }
1028 
1029 /*
1030  * Returns the index of the decimal-point in the given buffer (or -1 if not
1031  * found).
1032  */
1033 static int
DecimalPoint(char * buffer)1034 DecimalPoint(char *buffer)
1035 {
1036     char *dot = strchr(buffer, PERIOD);
1037 
1038     if (dot != 0)
1039 	return (int) (dot - buffer);
1040     return -1;
1041 }
1042 
1043 /*
1044  * Return the sequence-pointer of the left-parenthesis enclosing the given
1045  * operand-set at 'np'.
1046  */
1047 static DATA *
Balance(DATA * np,int level)1048 Balance(DATA * np, int level)
1049 {
1050     int target = level;
1051 
1052     while (np->prev != 0) {
1053 	if (np->cmd == R_PAREN)
1054 	    level++;
1055 	else if (np->psh)
1056 	    level--;
1057 	if (level <= target)
1058 	    break;		/* unbalanced */
1059 	np = np->prev;
1060     }
1061     return (level == 0) ? np : 0;
1062 }
1063 
1064 static void
ShowScriptName(void)1065 ShowScriptName(void)
1066 {
1067     while (screen_move_left(screen_col() + 1, 0) > 0) {
1068 	;
1069     }
1070     if (*top_output) {
1071 	screen_printf("script: %s", top_output);
1072     } else {
1073 	screen_puts("no script");
1074     }
1075     screen_clear_endline();
1076     (void) screen_getc();
1077 }
1078 
1079 /*
1080  * Edit an arbitrary buffer, starting at the current screen position.
1081  */
1082 static void
EditBuffer(char * buffer,int length)1083 EditBuffer(char *buffer, int length)
1084 {
1085     int end, chr;
1086     int col = (int) strlen(buffer);
1087     int done = FALSE;
1088     int offset = 0;
1089     int lmargin = screen_col();
1090     int shown = FALSE;
1091 
1092     end = screen_cols_left(lmargin);
1093     end = min(end, length);
1094     if (end < (int) strlen(buffer))
1095 	offset = (int) strlen(buffer) - end;
1096     while (!done) {
1097 	while (col - offset < 0) {
1098 	    offset--;
1099 	    shown = FALSE;
1100 	}
1101 	while (col - offset > end) {
1102 	    offset++;
1103 	    shown = FALSE;
1104 	}
1105 	if (isVisible() && !shown) {
1106 	    int x;
1107 	    screen_set_position(screen_row(), lmargin);
1108 	    screen_printf("%.*s", end, buffer + offset);
1109 	    if (screen_cols_left(x = screen_col()) > 0) {
1110 		screen_set_position(screen_row(), x);
1111 		screen_clear_endline();
1112 	    }
1113 	    screen_set_position(screen_row(), lmargin + col - offset);
1114 	    shown = TRUE;
1115 	}
1116 	chr = GetC();
1117 	if (isReturn(chr)) {
1118 	    done = TRUE;
1119 	} else if (isAscii(chr) && isprint(UCH(chr))) {
1120 	    if ((int) strlen(buffer) < length - 1)
1121 		col = InsertChar(buffer, chr, col, lmargin, 0, &offset);
1122 	    else
1123 		screen_alarm();
1124 	} else if (is_delete_left(chr)) {
1125 	    col = doDeleteChar(buffer, col, 0);
1126 	} else if (is_left_char(chr)) {
1127 	    col = screen_move_left(col, 0);
1128 	} else if (is_right_char(chr)) {
1129 	    col = screen_move_right(col, (int) strlen(buffer));
1130 	} else if (is_home_char(chr)) {
1131 	    while (col > 0)
1132 		col = screen_move_left(col, 0);
1133 	} else if (is_end_char(chr)) {
1134 	    while (col < (int) strlen(buffer))
1135 		col = screen_move_right(col, (int) strlen(buffer));
1136 	} else {
1137 	    screen_alarm();
1138 	}
1139     }
1140 }
1141 
1142 /*
1143  * Edit the comment-field
1144  */
1145 static void
EditComment(DATA * np)1146 EditComment(DATA * np)
1147 {
1148     char buffer[BUFSIZ];
1149     int row;
1150     int editcols[3];
1151 
1152     (void) strcpy(buffer, np->txt != 0 ? np->txt : "");
1153 
1154     ShowStatus(np, -1);
1155     row = ShowValue(np, editcols, TRUE) + 1;
1156     if (isVisible()) {
1157 	screen_set_position(row, editcols[1]);
1158 	screen_puts(" # ");
1159     }
1160     EditBuffer(buffer, sizeof(buffer));
1161     TrimString(buffer);
1162 
1163     if (*buffer != EOS || (np->txt != 0)) {
1164 	if (np->txt != 0) {
1165 	    if (!strcmp(buffer, np->txt))
1166 		return;		/* no change needed */
1167 	    free(np->txt);
1168 	}
1169 	np->txt = (*buffer != EOS) ? AllocString(buffer) : 0;
1170 	scriptCHG = TRUE;
1171     }
1172 }
1173 
1174 /*
1175  * Returns true if the given entry has editable data
1176  */
1177 static int
HasData(DATA * np)1178 HasData(DATA * np)
1179 {
1180     return !LastData(np) || (np->val != 0.0);
1181 }
1182 
1183 /*
1184  * Read a new number, until the operator for the next number is encountered.
1185  * Inputs:
1186  *	np	= line entry at which to prompt/read data
1187  *	edit	= true iff we re-edit prior contents of this line
1188  * Outputs:
1189  *	*len_	= -2 if right parenthesis found,
1190  *		= -1 if left parenthesis found,
1191  *		=  0 if no number found (usually to switch operators)
1192  *		= +n if value found, i.e., its length.
1193  *	*val_	= the decoded number.
1194  * Returns:
1195  *	The terminating character (e.g., an operator or command).
1196  */
1197 static int
EditValue(DATA * np,int * len_,Value * val_,int edit)1198 EditValue(DATA * np, int *len_, Value * val_, int edit)
1199 {
1200     int c;
1201     int row;
1202     int col;			/* current insert-position */
1203     int editcols[3];
1204     int done = FALSE;
1205     int was_visible = isVisible();
1206     int lmargin = screen_col();
1207     int nesting;		/* if we find left parenthesis rather than number */
1208     char buffer[MAXBFR];	/* current input value */
1209 
1210     static char old_digit = EOS;	/* nonzero iff we have pending digit */
1211 
1212     if (np == 0) {
1213 	*len_ = 0;
1214 	return 'q';
1215     }
1216 
1217     if (np->cmd == R_PAREN)
1218 	edit = FALSE;
1219 
1220     ShowStatus(np, FALSE);
1221     row = ShowValue(np, editcols, FALSE) + 1;
1222 
1223     if (isVisible()) {
1224 	screen_set_position(row, editcols[0]);
1225 	screen_set_reverse(TRUE);
1226     }
1227 
1228     if (edit) {
1229 	if (np->psh) {
1230 	    buffer[0] = L_PAREN;
1231 	    buffer[1] = EOS;
1232 	} else {
1233 	    int len, dot;
1234 	    char *s;
1235 
1236 	    (void) sprintf(buffer, "%0*.0f", len_frac, np->val);
1237 	    len = (int) strlen(buffer);
1238 	    s = buffer + len;
1239 	    s[1] = EOS;
1240 	    for (c = 0; c < len_frac; c++, s--)
1241 		s[0] = s[-1];
1242 	    dot = len - len_frac;
1243 	    buffer[dot] = PERIOD;
1244 	}
1245 	if (isVisible()) {
1246 	    screen_set_position(row, (int) (editcols[0]
1247 					    + use_width
1248 					    - (int) strlen(buffer)));
1249 	    screen_puts(buffer);
1250 	}
1251     } else {
1252 	buffer[0] = EOS;
1253     }
1254 
1255     if (isVisible())
1256 	screen_set_position(row, editcols[0] + use_width);
1257     col = (int) strlen(buffer);
1258     nesting = (*buffer == L_PAREN);
1259     c = EOS;
1260 
1261     while (!done) {
1262 	if (old_digit) {
1263 	    c = old_digit;
1264 	    old_digit = EOS;
1265 	} else {
1266 	    c = GetC();
1267 	}
1268 
1269 	/*
1270 	 * If the current operator is a right parenthesis, we must have
1271 	 * an operator following, with no data intervening:
1272 	 */
1273 	if (np->cmd == R_PAREN) {
1274 	    if (isDigit(c)
1275 		|| (c == PERIOD)
1276 		|| (c == L_PAREN)) {
1277 		screen_alarm();
1278 	    } else {
1279 		*len_ = -2;
1280 		done = TRUE;
1281 	    }
1282 	}
1283 	/*
1284 	 * Move left/right within the buffer to adjust the insertion
1285 	 * position. In curses mode, CTL/F and CTL/B are conflicting.
1286 	 */
1287 	else if (is_left_char(c) && !is_up_page(c)) {
1288 	    col = screen_move_left(col, 0);
1289 	} else if (is_right_char(c) && !is_down_page(c)) {
1290 	    col = screen_move_right(col, (int) strlen(buffer));
1291 	} else if (is_home_char(c)) {
1292 	    while (col > 0)
1293 		col = screen_move_left(col, 0);
1294 	} else if (is_end_char(c)) {
1295 	    while (col < (int) strlen(buffer))
1296 		col = screen_move_right(col, (int) strlen(buffer));
1297 	}
1298 	/*
1299 	 * Backspace deletes the last character entered:
1300 	 */
1301 	else if (is_delete_left(c)) {
1302 	    col = doDeleteChar(buffer, col, use_width);
1303 	    if (*buffer == EOS)
1304 		nesting = FALSE;
1305 	}
1306 	/*
1307 	 * A left parenthesis may be used only as the first (and only)
1308 	 * character of the operand.
1309 	 */
1310 	else if (c == L_PAREN) {
1311 	    if (*buffer != EOS) {
1312 		screen_alarm();
1313 	    } else {
1314 		col = InsertChar(buffer, c, col, lmargin, use_width, (int *) 0);
1315 		nesting = TRUE;
1316 	    }
1317 	}
1318 	/*
1319 	 * If we have received a left parenthesis, and do not delete
1320 	 * it, the next character begins a new operand-line:
1321 	 */
1322 	else if (nesting) {
1323 	    if (UnaryConflict(np, c))
1324 		screen_alarm();
1325 	    else {
1326 		if (isDigit(c) || c == PERIOD) {
1327 		    old_digit = (char) c;
1328 		    c = OP_ADD;
1329 		}
1330 		*len_ = -1;
1331 		done = TRUE;
1332 	    }
1333 	}
1334 	/*
1335 	 * Otherwise, we assume we have a normal value which we are
1336 	 * decoding:
1337 	 */
1338 	else if (isDigit(c)) {
1339 	    int limit = use_width;
1340 	    if (strchr(buffer, '.') == 0)
1341 		limit -= (1 + len_frac);
1342 	    if ((int) strlen(buffer) > limit)
1343 		screen_alarm();
1344 	    else
1345 		col = InsertChar(buffer, c, col, lmargin, use_width, (int *) 0);
1346 	}
1347 	/*
1348 	 * Decimal point can be entered once for each number. If we
1349 	 * get another, simply move it to the new position.
1350 	 */
1351 	else if (c == PERIOD) {
1352 	    int dot;
1353 	    if ((dot = DecimalPoint(buffer)) >= 0)
1354 		col = DeleteChar(buffer, dot, col, use_width);
1355 	    col = InsertChar(buffer, c, col, lmargin, use_width, (int *) 0);
1356 	}
1357 	/*
1358 	 * Otherwise, we assume a new operator-character for the
1359 	 * next command, flushing out the current command.  Decode
1360 	 * the number (if any) which we have read:
1361 	 */
1362 	else if (c != COMMA) {
1363 	    if (*buffer != EOS) {
1364 		int len = (char) strlen(buffer);
1365 		int dot;
1366 		Value cents;
1367 
1368 		if ((dot = DecimalPoint(buffer)) < 0)
1369 		    buffer[dot = len++] = PERIOD;
1370 		while ((len - dot) <= len_frac)
1371 		    buffer[len++] = '0';
1372 		len = dot + 1 + len_frac;	/* truncate */
1373 		buffer[len] = EOS;
1374 		(void) sscanf(&buffer[dot + 1], "%lf", &cents);
1375 		if (dot) {
1376 		    buffer[dot] = EOS;
1377 		    (void) sscanf(buffer, "%lf", val_);
1378 		    buffer[dot] = PERIOD;
1379 		    *val_ *= val_frac;
1380 		} else
1381 		    *val_ = 0.0;
1382 		*val_ += cents;
1383 	    } else {
1384 		*val_ = np->val;
1385 	    }
1386 	    *len_ = (*buffer == L_PAREN) ? 0 : (char) strlen(buffer);
1387 	    done = TRUE;
1388 	}
1389     }
1390     if (was_visible)
1391 	screen_set_reverse(FALSE);
1392     return (c);
1393 }
1394 
1395 /*
1396  * Return true if (given the length returned by 'EditValue()', and the
1397  * state of the data-list) we don't display a value.
1398  */
1399 static int
NoValue(int len)1400 NoValue(int len)
1401 {
1402     return ((len == 0 || len == -2) && (all_data->next->next != 0));
1403 }
1404 
1405 /*
1406  * Compute one stage of the running total.  To support parentheses, we use two
1407  * arguments: 'old' is the operand which contains the left parenthesis.
1408  */
1409 static int
Calculate(DATA * np,DATA * old)1410 Calculate(DATA * np, DATA * old)
1411 {
1412     Bool same;
1413     Value before = np->sum;
1414 
1415     np->sum = (old->prev) ? old->prev->sum : 0.0;
1416     switch (old->cmd) {
1417     default:
1418     case OP_ADD:
1419 	np->sum += np->val;
1420 	break;
1421     case OP_SUB:
1422 	np->sum -= np->val;
1423 	break;
1424     case OP_NEG:
1425 	np->sum = -np->sum;
1426 	break;
1427     case OP_MUL:
1428 	np->sum *= (np->val / val_frac);
1429 	np->sum = Floor(np->sum);
1430 	break;
1431     case OP_DIV:
1432 	if (np->val == 0.0) {
1433 	    if (np->sum > 0.0)
1434 		np->sum = (Value) big_long;
1435 	    else if (np->sum < 0.0)
1436 		np->sum = (Value) (-big_long);
1437 	} else {
1438 	    np->sum /= (np->val / val_frac);
1439 	    np->sum = Floor(np->sum);
1440 	}
1441 	break;
1442     case OP_INT:
1443 	np->aux = Ceiling(np->sum * np->val / (interval * 100. * val_frac));
1444 	np->sum += np->aux;
1445 	break;
1446     case OP_TAX:
1447 	np->aux = Ceiling(np->sum * np->val / (100. * val_frac));
1448 	np->sum += np->aux;
1449 	break;
1450     }
1451 
1452     same = (before == np->sum)
1453 	&& (before < big_long)
1454 	&& (before > -big_long);
1455 
1456     if (isVisible() && !same)
1457 	scriptCHG = TRUE;
1458     return (same);
1459 }
1460 
1461 /*
1462  * Given a pointer 'np' into the operand list, and (possibly) new 'cmd' and
1463  * 'val' components, propagate the computation to the end of the vector,
1464  * showing the result on the screen.
1465  */
1466 static void
Recompute(DATA * base)1467 Recompute(DATA * base)
1468 {
1469     DATA *np = base;
1470     DATA *op;
1471     int level = LevelOf(np);
1472     Bool same;
1473 
1474     while (np != 0) {
1475 	if (np->psh) {
1476 	    np->sum = 0.0;
1477 	    level++;
1478 	} else {
1479 	    if (np->cmd == R_PAREN) {
1480 		level--;
1481 		np->val = np->prev->sum;
1482 		op = Balance(np, level);
1483 		if (op == 0) {
1484 		    op = all_data;
1485 		    level = 0;
1486 		}
1487 	    } else {
1488 		op = np;
1489 	    }
1490 	    same = Calculate(np, op);
1491 	    if ((level == 0) && same)
1492 		break;
1493 	}
1494 	np = np->next;
1495     }
1496 
1497     ShowRange(base, np ? np->next : np);
1498 }
1499 
1500 /*
1501  * "Open" a new entry for editing.  If 'after' is set, we open the entry
1502  * after the current 'base'.  This is the normal mode of operation, and is
1503  * consistent with 'x'-command.
1504  */
1505 static DATA *
OpenLine(DATA * base,int after,int * repeated,int * edit)1506 OpenLine(DATA * base, int after, int *repeated, int *edit)
1507 {
1508     DATA *save_top = top_data;
1509     DATA *op = after ? base : base->prev;
1510     DATA *np;
1511     int chr;
1512     int this_row;
1513     int done = FALSE;
1514     int nested;
1515 
1516     np = AllocData(op);
1517     nested = LevelOf(np);
1518     this_row = CountFromTop(np);
1519 
1520     /* Adjust 'top_data' if we have to scroll a little */
1521     if (this_row <= 1) {
1522 	while (this_row++ <= 1) {
1523 	    if (top_data->prev == all_data)
1524 		break;
1525 	    top_data = top_data->prev;
1526 	}
1527     } else {
1528 	this_row -= (screen_full - 2);
1529 	while (this_row-- > 0)
1530 	    top_data = top_data->next;
1531     }
1532 
1533     /* (Re)display the screen with the opened line */
1534     if (top_data == save_top)
1535 	ShowFrom(np->next);
1536     else
1537 	ShowFrom(top_data);
1538     ShowStatus(np, TRUE + nested);
1539     screen_clear_endline();
1540     *edit = FALSE;		/* assume we'll get some new data */
1541 
1542     while (!done) {
1543 	chr = GetC();
1544 	if (UnaryConflict(np, chr)) {
1545 	    screen_alarm();
1546 	    continue;
1547 	}
1548 	switch (chr) {
1549 	case OP_INT:		/* sC: open to interest */
1550 	case OP_TAX:		/* sC: open to sales tax */
1551 	    /* patch: provide defaults */
1552 	case OP_ADD:		/* sC: open to add */
1553 	case OP_SUB:		/* sC: open to subtract */
1554 	case OP_NEG:		/* sC: open to negate */
1555 	case R_PAREN:		/* sC: open closing brace */
1556 	    setval(np, chr, 0.0, FALSE);
1557 	    done++;
1558 	    break;
1559 	case L_PAREN:
1560 	    setval(np, OP_ADD, 0.0, TRUE);
1561 	    *edit = TRUE;	/* force this to display */
1562 	    done++;
1563 	    break;
1564 	case OP_MUL:		/* sC: open to multiply */
1565 	case OP_DIV:		/* sC: open to divide */
1566 	    setval(np, chr, val_frac, FALSE);
1567 	    done++;
1568 	    break;
1569 	case 'a':
1570 	case 's':
1571 	case 'n':
1572 	case 'm':
1573 	case 'd':
1574 	case 'i':
1575 	case 't':
1576 	    chr = isRepeats(chr);
1577 	    setval(np, chr, LastVAL(np, chr), FALSE);
1578 	    done++;
1579 	    *repeated = TRUE;
1580 	    break;
1581 	case 'q':
1582 	case 'Q':
1583 	case 'o':
1584 	case 'O':
1585 	case 'x':
1586 	case 'X':
1587 	case 'u':
1588 	case 'U':
1589 	    (void) FreeData(np, FALSE);
1590 	    if (top_data != save_top) {
1591 		top_data = save_top;
1592 		ShowFrom(top_data);
1593 	    }
1594 	    np = base;
1595 	    done++;
1596 	    *edit = TRUE;	/* go back to the original */
1597 	    break;
1598 	default:
1599 	    screen_alarm();
1600 	}
1601     }
1602     return (np);
1603 }
1604 
1605 /*
1606  * Perform half/full-screen scrolling:
1607  */
1608 static DATA *
ScrollBy(DATA * np,int amount)1609 ScrollBy(DATA * np, int amount)
1610 {
1611     int last_seq = CountAllData();
1612     int this_seq = CountData(np);
1613     int next_seq;
1614     int top = CountData(top_data);
1615 
1616     if (amount > 0) {
1617 	if ((top + amount) < last_seq) {
1618 	    top += amount;
1619 	    next_seq = top;
1620 	} else
1621 	    next_seq = last_seq;
1622     } else {
1623 	if (this_seq > top)
1624 	    next_seq = top;
1625 	else {
1626 	    next_seq = top + amount;
1627 	    next_seq = max(next_seq, 1);
1628 	    top = next_seq;
1629 	}
1630     }
1631     ShowFrom(top_data = FindData(top));
1632     return FindData(next_seq);
1633 }
1634 
1635 /*
1636  * Compute a one-line movement of the cursor.  The 'amount' argument
1637  * compensates for other adjustments to the current data pointer in the calling
1638  * functions.
1639  */
1640 static DATA *
JumpBy(DATA * np,int amount)1641 JumpBy(DATA * np, int amount)
1642 {
1643     int this_seq = CountData(np);
1644     int next_seq = this_seq + amount;
1645     int last_seq = CountAllData();
1646     Bool end_flag = TRUE;
1647     Bool un_moved = FALSE;
1648 
1649     if (next_seq < 1) {
1650 	next_seq = 1;
1651     } else if (next_seq > last_seq) {
1652 	next_seq = last_seq;
1653     } else {
1654 	end_flag = FALSE;
1655     }
1656 
1657     if (next_seq != this_seq) {
1658 	np = FindData(next_seq);
1659     } else if (end_flag) {
1660 	un_moved = TRUE;
1661     }
1662 
1663     /* Figure out if we have to adjust the top_data variable.
1664      * If so, we've got to redisplay the screen.
1665      */
1666     if (!un_moved) {
1667 	int top = CountData(top_data);
1668 	Bool adjust = TRUE;
1669 	if (next_seq < top)
1670 	    top_data = np;
1671 	else if (next_seq >= top + screen_full)
1672 	    top_data = FindData(next_seq - screen_full + 1);
1673 	else
1674 	    adjust = FALSE;
1675 	if (adjust)
1676 	    ShowFrom(top_data);
1677     }
1678     return np;
1679 }
1680 
1681 /*
1682  * Jump to a specified entry, by number
1683  */
1684 static DATA *
JumpTo(DATA * np,int seq)1685 JumpTo(DATA * np, int seq)
1686 {
1687     return JumpBy(np, seq - CountData(np));
1688 }
1689 
1690 /*
1691  * Prompt/process a :-command
1692  */
1693 static DATA *
ColonCommand(DATA * np)1694 ColonCommand(DATA * np)
1695 {
1696     DATA *save_top = top_data;
1697     DATA *prior_np = np;
1698     char buffer[BUFSIZ];
1699     char *reply;
1700     static const char *last_write = "";
1701 
1702     if (CountFromTop(np) >= screen_full - 1) {
1703 	top_data = top_data->next;
1704 	ShowFrom(top_data);
1705     }
1706     ShowStatus(np, FALSE);	/* in case we have multiple prompts */
1707 
1708     screen_set_position(screen_full, 0);
1709     screen_putc(COLON);
1710     screen_putc(' ');
1711     *buffer = EOS;
1712     EditBuffer(buffer, sizeof(buffer));
1713     TrimString(buffer);
1714     reply = buffer;
1715     while (isspace(UCH(*reply)))
1716 	reply++;
1717 
1718     if (*reply != EOS) {
1719 	if (isdigit(UCH(*reply))) {
1720 	    char *dst;
1721 	    int seq = (int) strtol(reply, &dst, 0);
1722 	    np = JumpTo(np, seq);
1723 	} else {
1724 	    const char *param = reply + 1;
1725 	    while (isspace(UCH(*param)))
1726 		param++;
1727 	    switch (*reply) {
1728 	    case '$':		/* FALLTHRU */
1729 	    case '%':
1730 		np = JumpTo(np, CountAllData());
1731 		break;
1732 	    case 'e':
1733 		np = all_data->next;
1734 		while (np->next != 0)
1735 		    np = FreeData(np, TRUE);
1736 		save_top = top_data;
1737 		setval(np, OP_ADD, 0.0, FALSE);
1738 		Recompute(np);
1739 		if (Ok2Read(param))
1740 		    PushScripts(param);
1741 		break;
1742 	    case 'f':
1743 		ShowScriptName();
1744 		break;
1745 	    case 'r':
1746 		if (Ok2Read(param))
1747 		    PushScripts(param);
1748 		break;
1749 	    case 'w':
1750 		if (*param == EOS)
1751 		    param = last_write;
1752 		if (*param == EOS)
1753 		    param = top_output;
1754 		if (Ok2Write(param)) {
1755 		    last_write = AllocString(param);
1756 		    PutScript(param);
1757 		}
1758 		break;
1759 	    case 'x':
1760 		show_scripts = TRUE;
1761 		break;
1762 	    default:
1763 		screen_alarm();
1764 	    }
1765 	}
1766     }
1767 
1768     if (top_data != save_top
1769 	&& prior_np == np) {
1770 	top_data = save_top;
1771 	ShowFrom(top_data);
1772     }
1773     return np;
1774 }
1775 
1776 /*
1777  * Do simple screen movement. Note that some movement-commands may be
1778  * printing characters, so (if in edit-mode) we may have already intercepted
1779  * these as text.
1780  */
1781 static int
ScreenMovement(DATA ** pp,int chr)1782 ScreenMovement(DATA ** pp, int chr)
1783 {
1784     DATA *np = *pp;
1785     int ok = TRUE;
1786 
1787     if (chr == COLON) {
1788 	np = ColonCommand(np);
1789     } else if (chr == 'z') {
1790 	chr = GetC();
1791 	if (isReturn(chr)) {
1792 	    top_data = np;
1793 	} else {
1794 	    int top;
1795 	    int seq = CountData(np);
1796 	    if (chr == '-') {	/* use current entry as end */
1797 		top = seq - screen_full + 1;
1798 	    } else {
1799 		top = seq - screen_half + 1;
1800 	    }
1801 	    top = max(top, 1);
1802 	    top_data = FindData(top);
1803 	}
1804 	ShowFrom(top_data);
1805 #ifdef KEY_HOME
1806     } else if (chr == KEY_HOME) {	/* C: move to first entry in list */
1807 	np = all_data->next;
1808 	ShowFrom(top_data = np);
1809 #endif
1810 #ifdef KEY_END
1811     } else if (chr == KEY_END) {	/* C: move to last entry in list */
1812 	int top, seq;
1813 
1814 	np = EndOfData();
1815 	seq = CountData(np);
1816 	top = seq - screen_full + 1;
1817 	top = max(top, 1);
1818 	top_data = FindData(top);
1819 	ShowFrom(top_data);
1820 #endif
1821     } else if (chr == 'H') {	/* C: move to first entry on screen */
1822 	np = top_data;
1823     } else if (chr == 'L') {	/* C: move to last entry on screen */
1824 	np = ScreenBottom();
1825     } else if (is_down_char(chr)) {	/* C: move down 1 line */
1826 	np = JumpBy(np, 1);
1827     } else if (is_up_char(chr)) {	/* C: move up 1 line */
1828 	np = JumpBy(np, -1);
1829     } else if (chr == CTL('D')) {	/* C: scroll forward 1/2 screen */
1830 	np = ScrollBy(np, screen_half);
1831     } else if (chr == CTL('U')) {	/* C: scroll backward 1/2 screen */
1832 	np = ScrollBy(np, -screen_half);
1833     } else if (is_down_page(chr)) {	/* C: scroll forward one screen */
1834 	np = ScrollBy(np, screen_full);
1835     } else if (is_up_page(chr)) {	/* C: scroll backward one screen */
1836 	np = ScrollBy(np, -screen_full);
1837     } else {
1838 	ok = FALSE;
1839     }
1840 
1841     *pp = np;
1842     return ok;
1843 }
1844 
1845 /*
1846  * Display the help file.
1847  * We store the help-text as a special case of the data list to permit use
1848  * of the scrolling code.
1849  */
1850 static void
ShowHelp(void)1851 ShowHelp(void)
1852 {
1853     FILE *fp;
1854     DATA *save_data = all_data;
1855     DATA *save_top = top_data;
1856     DATA *np = 0;
1857     int chr;
1858     int end;
1859     int done = FALSE;
1860     char buffer[BUFSIZ];
1861 
1862     if ((all_data = all_help) == 0) {
1863 
1864 	np = AllocData((DATA *) 0);	/* header line not shown */
1865 
1866 	assert(all_data != 0);
1867 
1868 	if ((fp = fopen(helpfile, "r")) != 0) {
1869 	    while (fgets(buffer, sizeof(buffer), fp) != 0) {
1870 		np = AllocData(np);
1871 		np->txt = AllocString(buffer);
1872 	    }
1873 	    (void) fclose(fp);
1874 	} else {
1875 	    np = AllocData(np);
1876 	    np->txt =
1877 		AllocString("Could not find help-file.  Press 'q' to exit.");
1878 	}
1879     }
1880 
1881     np = top_data = all_data->next;
1882     end = CountAllData();
1883     ShowFrom(all_data);
1884 
1885     while (!done) {
1886 	screen_set_position(0, 0);
1887 	screen_set_bold(TRUE);
1888 	screen_clear_endline();
1889 	(void) sprintf(buffer, "line %d of %d", CountData(np), end);
1890 	screen_set_position(0, screen_cols_left((int) strlen(buffer)));
1891 	screen_puts(buffer);
1892 	screen_set_position(0, 0);
1893 	screen_printf("ADD %s -- %s -- ", RELEASE, copyrite);
1894 	screen_set_bold(FALSE);
1895 	screen_set_position(CountFromTop(np) + 1, 0);
1896 	chr = GetC();
1897 	if (chr == 'q' || chr == 'Q') {
1898 	    done = TRUE;
1899 	} else if (!ScreenMovement(&np, chr)) {
1900 	    screen_alarm();
1901 	}
1902     }
1903 
1904     all_help = all_data;
1905     all_data = save_data;
1906     top_data = save_top;
1907     ShowFrom(top_data);
1908 }
1909 
1910 #ifndef VMS
1911 
1912 # ifdef	unix
1913 #  define isSlash(c) ((c) == '/')
1914 # else
1915 #  define isSlash(c) ((c) == '/' || (c) == '\\')
1916 # endif
1917 
1918 static int
AbsolutePath(const char * path)1919 AbsolutePath(const char *path)
1920 {
1921 #if	!defined(unix) && !defined(vms)		/* assume MSDOS */
1922     if (isalpha(UCH(*path)) && path[1] == ':')
1923 	path += 2;
1924 #endif
1925     return isSlash(*path)
1926 	|| ((*path++ == '.')
1927 	    && (isSlash(*path)
1928 		|| (*path++ == '.' && isSlash(*path))));
1929 }
1930 
1931 static char *
PathLeaf(char * path)1932 PathLeaf(char *path)
1933 {
1934     int n;
1935     for (n = (int) strlen(path); n > 0; n--)
1936 	if (isSlash(path[n - 1]))
1937 	    break;
1938     return path + n;
1939 }
1940 #endif
1941 
1942 #ifndef ADD_HELPFILE
1943 #define ADD_HELPFILE "add.hlp"
1944 #endif
1945 
1946 /*
1947  * Find the help-file.  On UNIX and MSDOS, we look for the file in the same
1948  * directory as that in which this program is stored, located by searching the
1949  * PATH environment variable.
1950  */
1951 static void
FindHelp(const char * program)1952 FindHelp(const char *program)
1953 {
1954     char temp[BUFSIZ];
1955     const char *tail = ADD_HELPFILE;
1956     char *s = strcpy(temp, program);
1957 
1958 # if SYS_VMS
1959     if (strcspn(tail, "[]:") != strlen(tail)) {
1960 	strcpy(temp, tail);
1961 	s = temp + strlen(temp);
1962 	tail = "";
1963     } else {
1964 	for (s += strlen(temp); s != temp; s--)
1965 	    if (s[-1] == ']' || s[-1] == ':')
1966 		break;
1967     }
1968 # else /* assume UNIX or MSDOS */
1969     if (AbsolutePath(tail)) {
1970 	strcpy(temp, tail);
1971 	s = temp + strlen(temp);
1972 	tail = "";
1973     } else if (AbsolutePath(temp)) {
1974 	s = PathLeaf(temp);
1975     } else {
1976 	const char *path = getenv("PATH");
1977 	int j = 0, k, l;
1978 	if (path == 0)
1979 	    path = "";
1980 	while (path[j] != EOS) {
1981 	    for (k = j; path[k] != EOS && path[k] != PATHSEP; k++)
1982 		temp[k - j] = path[k];
1983 	    if ((l = k - j) != 0)
1984 		temp[l++] = '/';
1985 	    s = strcpy(temp + l, program);
1986 	    if (access(temp, 5) == 0) {
1987 		temp[l] = EOS;
1988 		break;
1989 	    }
1990 	    j = (path[k] != EOS) ? k + 1 : k;
1991 	}
1992 	if (path[j] == EOS) {
1993 	    s = temp;
1994 	    *s++ = '.';
1995 	    *s++ = '/';
1996 	    s = PathLeaf(strcpy(s, program));
1997 	}
1998     }
1999 # endif	/* VMS/UNIX/MSDOS */
2000     (void) strcpy(s, tail);
2001     helpfile = AllocString(temp);
2002 }
2003 
2004 /*
2005  * Main program loop: given 'old' operator (applies to current entry), read the
2006  * value 'val', delimited by the next operator 'chr'.
2007  */
2008 static int
Loop(void)2009 Loop(void)
2010 {
2011     DATA *np = EndOfData();
2012     Value val;
2013     int test_c;
2014     int chr;
2015     int len;
2016     int opened;
2017     int repeated;		/* if true, 'Loop' assumes editable value */
2018     int edit = FALSE;
2019 
2020     for (;;) {
2021 	chr = EditValue(np, &len, &val, edit);
2022 	switch (chr) {
2023 	case '\t':
2024 	    chr = EQUALS;
2025 	    break;
2026 	case CTL('P'):
2027 	    chr = 'k';
2028 	    break;
2029 	case CTL('N'):
2030 	case '\r':
2031 	case '\n':
2032 	    chr = 'j';
2033 	    break;
2034 	case ' ':
2035 	    chr = DefaultOp(np);
2036 	    break;
2037 	}
2038 
2039 	if (chr == CTL('G')) {
2040 	    ShowScriptName();
2041 	} else if (chr == 'X') {	/* C: delete current entry, move up */
2042 	    if (NoValue(len)) {
2043 		np = FreeData(np, TRUE);
2044 		np = JumpBy(np, -1);
2045 		edit = HasData(np);
2046 	    } else {
2047 		edit = FALSE;
2048 	    }
2049 	} else if (chr == 'x') {	/* C: delete current entry, move down */
2050 	    if (NoValue(len)) {
2051 		np = FreeData(np, TRUE);
2052 		edit = HasData(np);
2053 	    } else {
2054 		edit = FALSE;
2055 	    }
2056 	} else if (chr == 'u') {	/* C: undo last 'x' command */
2057 	    if (HasData(np))
2058 		edit = TRUE;
2059 	    else
2060 		screen_alarm();
2061 	} else if ((test_c = isToggles(chr)) != EOS) {
2062 	    /* C: toggle current operator */
2063 	    chr = test_c;
2064 	    if (UnaryConflict(np, chr)) {
2065 		screen_alarm();
2066 	    } else {
2067 		if (len == 0)
2068 		    val = np->val;
2069 		else
2070 		    edit = TRUE;
2071 		setval(np, chr, val, np->psh);
2072 	    }
2073 	} else {
2074 	    if (len != 0) {
2075 		setval(np, np->cmd, val, (len == -1));
2076 		if (LastData(np) && isCommand(chr)) {
2077 		    (void) AllocData(np);
2078 		    np->next->cmd = (char) chr;
2079 		} else if (LastData(np) && isRepeats(chr)) {
2080 		    (void) AllocData(np);
2081 		    np->next->cmd = (char) isRepeats(chr);
2082 		}
2083 		Recompute(np);
2084 	    } else {
2085 		(void) ShowValue(np, (int *) 0, FALSE);
2086 	    }
2087 
2088 	    repeated = FALSE;
2089 	    opened = FALSE;
2090 
2091 	    if (chr == '?') {	/* C: display help file */
2092 		ShowHelp();
2093 	    } else if (chr == '#') {	/* C: edit comment */
2094 		EditComment(np);
2095 	    } else if (chr == 'Q') {	/* C: quit w/o changes */
2096 		return (FALSE);
2097 	    } else if (chr == 'q') {	/* C: quit */
2098 		return (TRUE);
2099 	    } else if (ScreenMovement(&np, chr)) {
2100 		;
2101 	    } else if (chr == 'O'	/* C: open before */
2102 		       || chr == 'o') {		/* C: open after */
2103 		opened = TRUE;
2104 		np = OpenLine(np, (chr == 'o'),
2105 			      &repeated, &edit);
2106 		if (repeated)
2107 		    chr = np->cmd;
2108 	    } else {
2109 		if ((test_c = isRepeats(chr)) != EOS) {
2110 		    chr = test_c;
2111 		    repeated = TRUE;
2112 		}
2113 		if (isCommand(chr)) {
2114 		    np = JumpBy(np, 1);
2115 		    np->cmd = (char) chr;
2116 		} else if (chr == 'w' && use_width < max_width) {
2117 		    ++use_width;
2118 		    ShowFrom(top_data);
2119 		} else if (chr == 'W' && use_width > 6) {
2120 		    --use_width;
2121 		    ShowFrom(top_data);
2122 		} else if (chr == EQUALS) {
2123 		    Recompute(np);
2124 		} else {
2125 		    screen_alarm();
2126 		}
2127 	    }
2128 	    if (repeated) {
2129 		edit = TRUE;
2130 		setval(np, chr, LastVAL(np, chr), FALSE);
2131 	    } else if (!opened) {
2132 		edit = HasData(np);
2133 	    }
2134 	}
2135     }
2136 }
2137 
2138 static void
usage(void)2139 usage(void)
2140 {
2141     static const char *tbl[] =
2142     {
2143 	"Usage: add [options] [scripts]"
2144 	,""
2145 	,"Options:"
2146 	,"  -h           print this message"
2147 	,"  -i interval  specify compounding-interval (default=12)"
2148 	,"  -o script    specify output-script name (default is the first"
2149 	,"               input-script name)"
2150 	,"  -p num       specify precision (default=2)"
2151 	,"  -V           print the version"
2152 	,""
2153 	,"Description:"
2154 	,"  Script-based adding machine that allows you to edit the operations"
2155 	,"  and data."
2156     };
2157     unsigned j;
2158 
2159     for (j = 0; j < SIZEOF(tbl); j++)
2160 	fprintf(stderr, "%s\n", tbl[j]);
2161     exit(EXIT_FAILURE);
2162 }
2163 
2164 #if !HAVE_GETOPT
2165 int optind;
2166 int optchr;
2167 char *optarg;
2168 
2169 int
getopt(int argc,char ** argv,char * opts)2170 getopt(int argc, char **argv, char *opts)
2171 {
2172     if (++optind < argc
2173 	&& (optarg = argv[optind]) != 0
2174 	&& *optarg == '-') {
2175 	if (*(++optarg) != ':'
2176 	    && (optchr = *optarg) != EOS
2177 	    && strchr(opts, *(optarg++)) != 0)
2178 	    return optchr;
2179     }
2180     return EOF;
2181 }
2182 #endif
2183 
2184 int
main(int argc,char ** argv)2185 main(int argc, char **argv)
2186 {
2187     long k;
2188     int j;
2189     int max_digits;		/* maximum length of a number */
2190     int changed;
2191     char tmp;
2192 
2193     (void) signal(SIGFPE, SIG_IGN);
2194     len_frac = 2;
2195     interval = 12;
2196 
2197     /*
2198      * Compute the maximum number of digits to display:
2199      *
2200      *      big_long - the maximum positive value that we can stuff into
2201      *                 a 'long'. Assume symmetry, i.e., that we can use
2202      *                 the same negative magnitude.
2203      *      max_width - the maximum length of a formatted number, counting
2204      *                 sign, decimal point and commas between groups
2205      *                 of digits.
2206      */
2207     big_long = k = 1;
2208     while ((k = (k << 1) + 1) > big_long)
2209 	big_long = k;
2210 
2211     for (k = big_long, max_digits = 0; k >= 10; k /= 10)
2212 	max_digits++;
2213 
2214     FindHelp(argv[0]);
2215 
2216     while ((j = getopt(argc, argv, "hi:o:p:V")) != EOF)
2217 	switch (j) {
2218 	case 'p':
2219 	    if ((sscanf(optarg, "%d%c", &len_frac, &tmp) != 1)
2220 		|| (len_frac <= 0 || len_frac > max_digits - 2)) {
2221 		fprintf(stderr, "Option p limited to 0..%d\n",
2222 			max_digits - 2);
2223 		usage();
2224 	    }
2225 	    break;
2226 	case 'i':
2227 	    if ((sscanf(optarg, "%d%c", &interval, &tmp) != 1)
2228 		|| (interval <= 0 || interval > 100)) {
2229 		fprintf(stderr, "Option i limited to 0..100\n");
2230 		usage();
2231 	    }
2232 	    break;
2233 	case 'o':
2234 	    top_output = optarg;
2235 	    break;
2236 	case 'h':
2237 	default:
2238 	    usage();
2239 	case 'V':
2240 	    puts(RELEASE);
2241 	    return EXIT_SUCCESS;
2242 	}
2243 
2244     for (j = 0, val_frac = 1.0; j < len_frac; j++)
2245 	val_frac *= 10.0;
2246 
2247     max_width = 1 + ((max_digits - len_frac) + 2) / 3 + max_digits + 1;
2248     use_width = max_width;
2249     if (use_width > 20)
2250 	use_width = 20;
2251 
2252     /*
2253      * Allocate some dummy data so we can propagate results from it.
2254      */
2255     all_data = 0;
2256     for (j = 0; j < 2; j++) {
2257 	setval(AllocData((DATA *) 0), OP_ADD, 0.0, FALSE);
2258     }
2259     top_data = all_data->next;
2260 
2261     /*
2262      * If we have input scripts, save a pointer to the list:
2263      */
2264     if (optind < argc) {
2265 	if (top_output == 0
2266 	    && !Fexists(argv[optind]))
2267 	    top_output = argv[optind++];
2268 	scriptv = argv + optind;
2269 	for (j = 0; scriptv[j] != 0; j++) {
2270 	    (void) Ok2Read(scriptv[j]);
2271 	}
2272     } else {
2273 	scriptv = argv + argc;	/* points to a null-pointer */
2274     }
2275 
2276     /*
2277      * Get the default output-filename
2278      */
2279     if (optind < argc && !top_output)
2280 	top_output = argv[argc - 1];
2281 
2282     if (top_output == 0)
2283 	top_output = "";
2284 
2285     /*
2286      * Setup and run the interactive portion of the program.
2287      */
2288     screen_start();
2289     changed = Loop();
2290     screen_finish();
2291 
2292     /*
2293      * If one or more scripts were given as input, and a '-o' argument
2294      * was given, overwrite the last one with the results.
2295      */
2296     if (*top_output && changed && scriptCHG)
2297 	PutScript(top_output);
2298 
2299 #if NO_LEAKS
2300     free(helpfile);
2301     while (FreeData(all_data, FALSE) != 0)
2302 	/*EMPTY */ ;
2303 #if HAVE_DBMALLOC_H
2304     free(-1);			/* FIXME: force linux+dbmalloc to report */
2305 #endif
2306 #endif
2307 
2308     return (EXIT_SUCCESS);
2309 }
2310