1 /*
2  * Standard routines for Mathomatic.
3  *
4  * Copyright (C) 1987-2012 George Gesslein II.
5 
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10 
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 Lesser General Public License for more details.
15 
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19 
20 The chief copyright holder can be contacted at gesslein@mathomatic.org, or
21 George Gesslein II, P.O. Box 224, Lansing, NY  14882-0224  USA.
22 
23  */
24 
25 #include "includes.h"
26 #if	UNIX || CYGWIN
27 #include <sys/ioctl.h>
28 #include <termios.h>
29 #endif
30 
31 /*
32  * Display the main Mathomatic startup message.
33  * fp is the output file stream pointer it goes to, and should be stdout or gfp,
34  * but really can go to any file you wish.
35  */
36 void
display_startup_message(fp)37 display_startup_message(fp)
38 FILE	*fp;	/* output file pointer */
39 {
40 	long	es_size;
41 
42 #if	SECURE
43 	fprintf(fp, _("Secure "));
44 #else
45 	if (security_level >= 2)
46 		fprintf(fp, _("Secure "));
47 	else if (security_level == -1)
48 		fprintf(fp, "m4 ");
49 #endif
50 	fprintf(fp, "Mathomatic version %s\n", VERSION);
51 	if (html_flag)
52 		fprintf(fp, "Copyright &copy; 1987-2012 George Gesslein II.\n");
53 	else
54 		fprintf(fp, "Copyright (C) 1987-2012 George Gesslein II.\n");
55 	es_size = (long) n_tokens * sizeof(token_type) * 2L / 1000L;
56 	if (es_size >= 1000) {
57 		fprintf(fp, _("%d equation spaces available in RAM; %ld megabytes per equation space.\n"),
58 		    N_EQUATIONS, (es_size + 500L) / 1000L);
59 	} else {
60 		fprintf(fp, _("%d equation spaces available in RAM; %ld kilobytes per equation space.\n"),
61 		    N_EQUATIONS, es_size);
62 	}
63 }
64 
65 /*
66  * Standard function to report an error to the user.
67  */
68 void
error(str)69 error(str)
70 const char	*str;		/* constant string to display */
71 {
72 	error_str = str;	/* save reference to str, must be a constant string, temporary strings don't work */
73 #if	!SILENT && !LIBRARY
74 	set_color(2);		/* set color to red */
75 	printf("%s\n", str);
76 	default_color(false);	/* restore to default color */
77 #endif
78 }
79 
80 /*
81  * Reset last call to error(), as if it didn't happen.
82  */
83 void
reset_error(void)84 reset_error(void)
85 {
86 #if	!SILENT && !LIBRARY
87 	if (error_str)
88 		printf(_("Forgetting previous error.\n"));
89 #endif
90 	error_str = NULL;
91 }
92 
93 /*
94  * Standard function to report a warning only once to the user.
95  * A warning is less serious than an error.
96  */
97 void
warning(str)98 warning(str)
99 const char	*str;		/* constant string to display */
100 {
101 	int	already_warned = false;
102 
103 	if (warning_str) {
104 		if (strcmp(str, warning_str) == 0)
105 			already_warned = true;
106 	}
107 	warning_str = str;	/* save reference to str, must be a constant string, temporary strings don't work */
108 #if	!SILENT && !LIBRARY
109 	if (!already_warned && debug_level >= -1) {
110 		set_color(1);		/* set color to yellow */
111 		printf("Warning: %s\n", str);
112 		default_color(false);	/* restore to default color */
113 	}
114 #endif
115 }
116 
117 /*
118  * This is called when the maximum expression size has been exceeded.
119  *
120  * There is no return.
121  */
122 void
error_huge(void)123 error_huge(void)
124 {
125 	longjmp(jmp_save, 14);
126 }
127 
128 /*
129  * This is called when a bug test result is positive.
130  *
131  * There is no return.
132  */
133 void
error_bug(str)134 error_bug(str)
135 const char	*str;	/* constant string to display */
136 {
137 /* Return and display the passed error message in str. */
138 	error(str);	/* str must be a constant string, temporary strings don't work */
139 #if	SILENT || LIBRARY
140 	printf("%s\n", str);
141 #endif
142 	printf(_("Please report this bug to the maintainers,\n"));
143 	printf(_("along with the entry sequence that caused it.\n"));
144 #if	!LIBRARY
145 	printf(_("Type \"help bugs\" for info on how to report bugs found in this program.\n"));
146 #endif
147 	longjmp(jmp_save, 13);	/* Abort the current operation with the critical error number 13. */
148 }
149 
150 /*
151  * Check if a floating point math function flagged an error.
152  *
153  * There is no return if an error message is displayed.
154  */
155 void
check_err(void)156 check_err(void)
157 {
158 	switch (errno) {
159 	case EDOM:
160 		errno = 0;
161 		if (domain_check) {
162 			domain_check = false;
163 		} else {
164 			error(_("Domain error in constant."));
165 			longjmp(jmp_save, 2);
166 		}
167 		break;
168 	case ERANGE:
169 		errno = 0;
170 		error(_("Floating point constant out of range."));
171 		longjmp(jmp_save, 2);
172 		break;
173 	}
174 }
175 
176 /*
177  * Get the current screen (window) width and height from the operating system.
178  *
179  * Return true if the global integers screen_columns and/or screen_rows were set.
180  */
181 int
get_screen_size(void)182 get_screen_size(void)
183 {
184 	int	rv = false;
185 
186 #if	UNIX || CYGWIN
187 	struct winsize	ws;
188 
189 	ws.ws_col = 0;
190 	ws.ws_row = 0;
191 	if (ioctl(1, TIOCGWINSZ, &ws) >= 0) {
192 		if (ws.ws_col > 0) {
193 			screen_columns = ws.ws_col;
194 			rv = true;
195 		}
196 		if (ws.ws_row > 0) {
197 			screen_rows = ws.ws_row;
198 			rv = true;
199 		}
200 	}
201 #else
202 	screen_columns = STANDARD_SCREEN_COLUMNS;
203 	screen_rows = STANDARD_SCREEN_ROWS;
204 	rv = true;
205 #endif
206 	return rv;
207 }
208 
209 /*
210  * Allocate the display lines in the vscreen[] array.
211  * Call this before using vscreen[].
212  *
213  * Return true with vscreen[] allocated to TEXT_ROWS*current_columns characters if successful.
214  */
215 int
malloc_vscreen(void)216 malloc_vscreen(void)
217 {
218 	int	i;
219 
220 	if (current_columns == 0 || ((screen_columns > 0) ? (current_columns != screen_columns) : (current_columns != TEXT_COLUMNS))) {
221 		if (screen_columns > 0) {
222 			current_columns = screen_columns;
223 		} else {
224 			current_columns = TEXT_COLUMNS;
225 		}
226 		for (i = 0; i < TEXT_ROWS; i++) {
227 			if (vscreen[i]) {
228 				free(vscreen[i]);
229 			}
230 			vscreen[i] = malloc(current_columns + 1);
231 			if (vscreen[i] == NULL) {
232 				error(_("Out of memory (can't malloc(3))."));
233 				current_columns = 0;
234 				return false;
235 			}
236 		}
237 	}
238 	return true;
239 }
240 
241 /*
242  * Allocate the needed global expression storage arrays.
243  * Each is static and can hold n_tokens elements.
244  * n_tokens must not change until Mathomatic terminates.
245  *
246  * init_mem() is called only once upon Mathomatic startup
247  * before using the symbolic math engine,
248  * and can be undone by calling free_mem() below.
249  *
250  * Returns true if successful, otherwise Mathomatic cannot be used.
251  */
252 int
init_mem(void)253 init_mem(void)
254 {
255 	if (n_tokens <= 0)
256 		return false;
257 	if ((scratch = (token_type *) malloc(((n_tokens * 3) / 2) * sizeof(token_type))) == NULL
258 	    || (tes = (token_type *) malloc(n_tokens * sizeof(token_type))) == NULL
259 	    || (tlhs = (token_type *) malloc(n_tokens * sizeof(token_type))) == NULL
260 	    || (trhs = (token_type *) malloc(n_tokens * sizeof(token_type))) == NULL) {
261 		return false;
262 	}
263 	if (alloc_next_espace() < 0) {	/* make sure there is at least 1 equation space */
264 		return false;
265 	}
266 	clear_all();
267 	return true;
268 }
269 
270 #if	LIBRARY || VALGRIND
271 /*
272  * Free the global expression storage arrays and other known memory buffers.
273  * After calling this, memory usage is reset and Mathomatic becomes unusable,
274  * so do not call unless finished with using the Mathomatic code.
275  *
276  * This routine is usually not needed, because when a program exits,
277  * all the memory it allocated is released by the operating system.
278  * Inclusion of this routine was requested by Tam Hanna for use with Symbian OS.
279  */
280 void
free_mem(void)281 free_mem(void)
282 {
283 	int	i;
284 
285 	clear_all();
286 
287 	free(scratch);
288 	free(tes);
289 	free(tlhs);
290 	free(trhs);
291 
292 	for (i = 0; i < N_EQUATIONS; i++) {
293 		if (lhs[i]) {
294 			free(lhs[i]);
295 			lhs[i] = NULL;
296 		}
297 		if (rhs[i]) {
298 			free(rhs[i]);
299 			rhs[i] = NULL;
300 		}
301 	}
302 	n_equations = 0;
303 
304 	for (i = 0; i < TEXT_ROWS; i++) {
305 		if (vscreen[i]) {
306 			free(vscreen[i]);
307 			vscreen[i] = NULL;
308 		}
309 	}
310 	current_columns = 0;
311 }
312 #endif
313 
314 #if	DEBUG
315 /*
316  * Use this function to check for any erroneous global conditions.
317  * Use of this function is rather paranoid, but helpful.
318  *
319  * Always returns true, or doesn't return on error.
320  */
321 int
check_gvars(void)322 check_gvars(void)
323 {
324 	if (!(domain_check == false &&
325 	high_prec == false &&
326 	partial_flag == true &&
327 	symb_flag == false &&
328 	sign_cmp_flag == false &&
329 	approximate_roots == false))
330 		error_bug("Global vars got changed!");
331 
332 	if (!(zero_token.level == 1 &&
333 	zero_token.kind == CONSTANT &&
334 	zero_token.token.constant == 0.0 &&
335 	one_token.level == 1 &&
336 	one_token.kind == CONSTANT &&
337 	one_token.token.constant == 1.0))
338 		error_bug("Global constants got changed!");
339 
340 	return true;
341 }
342 #endif
343 
344 /*
345  * Initialize some important global variables to their defaults.
346  * This is called on startup and by process() to reset the global flags to the default state.
347  * This is also called when processing is aborted with a longjmp(3).
348  */
349 void
init_gvars(void)350 init_gvars(void)
351 {
352 	domain_check = false;
353 	high_prec = false;
354 	partial_flag = true;
355 	symb_flag = false;
356 	sign_cmp_flag = false;
357 	approximate_roots = false;
358 	repeat_flag = false;
359 
360 	/* initialize the universal and often used constant "0" expression */
361 	zero_token.level = 1;
362 	zero_token.kind = CONSTANT;
363 	zero_token.token.constant = 0.0;
364 
365 	/* initialize the universal and often used constant "1" expression */
366 	one_token.level = 1;
367 	one_token.kind = CONSTANT;
368 	one_token.token.constant = 1.0;
369 }
370 
371 /*
372  * Clean up when processing is unexpectedly interrupted or terminated.
373  */
374 void
clean_up(void)375 clean_up(void)
376 {
377 	int	i;
378 
379 	init_gvars();		/* reset the global variables to the default */
380 	if (gfp != default_out) {	/* reset the output file to default */
381 #if	!SECURE
382 		if (gfp != stdout && gfp != stderr)
383 			fclose(gfp);
384 #endif
385 		gfp = default_out;
386 	}
387 	gfp_filename = NULL;
388 	for (i = 0; i < n_equations; i++) {
389 		if (n_lhs[i] <= 0) {
390 			n_lhs[i] = 0;
391 			n_rhs[i] = 0;
392 		}
393 	}
394 }
395 
396 /*
397  * Register all sign variables in all equation spaces
398  * so that the next sign variables returned by next_sign() will be unique.
399  */
400 void
set_sign_array(void)401 set_sign_array(void)
402 {
403 	int	i, j;
404 
405 	CLEAR_ARRAY(sign_array);
406 	for (i = 0; i < n_equations; i++) {
407 		if (n_lhs[i] > 0) {
408 			for (j = 0; j < n_lhs[i]; j += 2) {
409 				if (lhs[i][j].kind == VARIABLE && (lhs[i][j].token.variable & VAR_MASK) == SIGN) {
410 					sign_array[(lhs[i][j].token.variable >> VAR_SHIFT) & SUBSCRIPT_MASK] = true;
411 				}
412 			}
413 			for (j = 0; j < n_rhs[i]; j += 2) {
414 				if (rhs[i][j].kind == VARIABLE && (rhs[i][j].token.variable & VAR_MASK) == SIGN) {
415 					sign_array[(rhs[i][j].token.variable >> VAR_SHIFT) & SUBSCRIPT_MASK] = true;
416 				}
417 			}
418 		}
419 	}
420 }
421 
422 /*
423  * Return next unused sign variable in "*vp".
424  * Mark it used.
425  */
426 int
next_sign(vp)427 next_sign(vp)
428 long	*vp;
429 {
430 	int	i;
431 
432 	for (i = 0;; i++) {
433 		if (i >= ARR_CNT(sign_array)) {
434 			/* out of unique sign variables */
435 			*vp = SIGN;
436 			return false;
437 		}
438 		if (!sign_array[i]) {
439 			*vp = SIGN + (((long) i) << VAR_SHIFT);
440 			sign_array[i] = true;
441 			break;
442 		}
443 	}
444 	return true;
445 }
446 
447 /*
448  * Erase all equation spaces and initialize the global variables.
449  * Similar to a restart.
450  */
451 void
clear_all(void)452 clear_all(void)
453 {
454 	int	i;
455 
456 /* select first equation space */
457 	cur_equation = 0;
458 /* erase all equation spaces by setting their length to zero */
459 	CLEAR_ARRAY(n_lhs);
460 	CLEAR_ARRAY(n_rhs);
461 /* forget all variables names */
462 	for (i = 0; var_names[i]; i++) {
463 		free(var_names[i]);
464 		var_names[i] = NULL;
465 	}
466 /* reset everything to a known state */
467 	CLEAR_ARRAY(sign_array);
468 	init_gvars();
469 }
470 
471 /*
472  * Return true if the specified equation space is available,
473  * zeroing and allocating if necessary.
474  */
475 int
alloc_espace(i)476 alloc_espace(i)
477 int	i;	/* equation space number */
478 {
479 	if (i < 0 || i >= N_EQUATIONS)
480 		return false;
481 	n_lhs[i] = 0;
482 	n_rhs[i] = 0;
483 	if (lhs[i] && rhs[i])
484 		return true;	/* already allocated */
485 	if (lhs[i] || rhs[i])
486 		return false;	/* something is wrong */
487 	lhs[i] = (token_type *) malloc(n_tokens * sizeof(token_type));
488 	if (lhs[i] == NULL)
489 		return false;
490 	rhs[i] = (token_type *) malloc(n_tokens * sizeof(token_type));
491 	if (rhs[i] == NULL) {
492 		free(lhs[i]);
493 		lhs[i] = NULL;
494 		return false;
495 	}
496 	return true;
497 }
498 
499 /*
500  * Allocate all equation spaces up to and including an equation space number,
501  * making sure the specified equation number is valid and usable.
502  *
503  * Returns true if successful.
504  */
505 int
alloc_to_espace(en)506 alloc_to_espace(en)
507 int	en;	/* equation space number */
508 {
509 	if (en < 0 || en >= N_EQUATIONS)
510 		return false;
511 	for (;;) {
512 		if (en < n_equations)
513 			return true;
514 		if (n_equations >= N_EQUATIONS)
515 			return false;
516 		if (!alloc_espace(n_equations)) {
517 			warning(_("Memory is exhausted."));
518 			return false;
519 		}
520 		n_equations++;
521 	}
522 }
523 
524 /*
525  * Allocate or reuse an empty equation space.
526  *
527  * Returns empty equation space number ready for use or -1 on error.
528  */
529 int
alloc_next_espace(void)530 alloc_next_espace(void)
531 {
532 	int	i, n;
533 
534 	for (n = cur_equation, i = 0;; n = (n + 1) % N_EQUATIONS, i++) {
535 		if (i >= N_EQUATIONS)
536 			return -1;
537 		if (n >= n_equations) {
538 			n = n_equations;
539 			if (!alloc_espace(n)) {
540 				warning(_("Memory is exhausted."));
541 				for (n = 0; n < n_equations; n++) {
542 					if (n_lhs[n] == 0) {
543 						n_rhs[n] = 0;
544 						return n;
545 					}
546 				}
547 				return -1;
548 			}
549 			n_equations++;
550 			return n;
551 		}
552 		if (n_lhs[n] == 0)
553 			break;
554 	}
555 	n_rhs[n] = 0;
556 	return n;
557 }
558 
559 /*
560  * Return the number of the next empty equation space, otherwise don't return.
561  */
562 int
next_espace(void)563 next_espace(void)
564 {
565 	int		i, j;
566 	long		answer_v = 0;		/* Mathomatic answer variable */
567 
568 	i = alloc_next_espace();
569 	if (i < 0) {
570 #if	!SILENT
571 		printf(_("Deleting old numeric calculations to free up equation spaces.\n"));
572 #endif
573 		parse_var(&answer_v, "answer");	/* convert to a Mathomatic variable */
574 		for (j = 0; j < n_equations; j++) {
575 			if (n_lhs[j] == 1 && lhs[j][0].kind == VARIABLE
576 			    && lhs[j][0].token.variable == answer_v) {
577 				/* delete calculation from memory */
578 				n_lhs[j] = 0;
579 				n_rhs[j] = 0;
580 			}
581 		}
582 		i = alloc_next_espace();
583 		if (i < 0) {
584 			error(_("Out of free equation spaces."));
585 #if	!SILENT
586 			printf(_("Use the clear command on unnecessary equations and try again.\n"));
587 #endif
588 			longjmp(jmp_save, 3);	/* do not return */
589 		}
590 	}
591 	return i;
592 }
593 
594 /*
595  * Copy equation space "src" to equation space "dest".
596  * "dest" is overwritten.
597  */
598 void
copy_espace(src,dest)599 copy_espace(src, dest)
600 int	src, dest;	/* equation space numbers */
601 {
602 	if (src == dest) {
603 #if	DEBUG
604 		error_bug("Internal error: copy_espace() source and destination the same.");
605 #endif
606 		return;
607 	}
608 	blt(lhs[dest], lhs[src], n_lhs[src] * sizeof(token_type));
609 	n_lhs[dest] = n_lhs[src];
610 	blt(rhs[dest], rhs[src], n_rhs[src] * sizeof(token_type));
611 	n_rhs[dest] = n_rhs[src];
612 }
613 
614 /*
615  * Return true if equation space "i" is a valid equation solved for a normal variable.
616  */
617 int
solved_equation(i)618 solved_equation(i)
619 int	i;
620 {
621 	if (empty_equation_space(i))
622 		return false;
623 	if (n_rhs[i] <= 0)
624 		return false;
625 	if (n_lhs[i] != 1 || lhs[i][0].kind != VARIABLE || (lhs[i][0].token.variable & VAR_MASK) <= SIGN)
626 		return false;
627 	if (found_var(rhs[i], n_rhs[i], lhs[i][0].token.variable))
628 		return false;
629 	return true;
630 }
631 
632 /*
633  * Return the number of times variable "v" is found in an expression.
634  */
635 int
found_var(p1,n,v)636 found_var(p1, n, v)
637 token_type	*p1;	/* expression pointer */
638 int		n;	/* expression length */
639 long		v;	/* standard Mathomatic variable */
640 {
641 	int	j;
642 	int	count = 0;
643 
644 	if (v) {
645 		for (j = 0; j < n; j++) {
646 			if (p1[j].kind == VARIABLE && p1[j].token.variable == v) {
647 				count++;
648 			}
649 		}
650 	}
651 	return count;
652 }
653 
654 /*
655  * Return true if variable "v" exists in equation space "i".
656  */
657 int
var_in_equation(i,v)658 var_in_equation(i, v)
659 int	i;	/* equation space number */
660 long	v;	/* standard Mathomatic variable */
661 {
662 	if (empty_equation_space(i))
663 		return false;
664 	if (found_var(lhs[i], n_lhs[i], v))
665 		return true;
666 	if (n_rhs[i] <= 0)
667 		return false;
668 	if (found_var(rhs[i], n_rhs[i], v))
669 		return true;
670 	return false;
671 }
672 
673 /*
674  * Return true if variable "v" exists in any equation space.
675  *
676  * Search forward starting at the next equation space if forward_direction is true,
677  * otherwise search backwards starting at the previous equation space.
678  * If found, return true with cur_equation set to the equation space the variable is found in.
679  */
680 int
search_all_for_var(v,forward_direction)681 search_all_for_var(v, forward_direction)
682 long	v;
683 int	forward_direction;
684 {
685 	int	i, n;
686 
687 	i = cur_equation;
688 	for (n = 0; n < n_equations; n++) {
689 		if (forward_direction) {
690 			if (i >= (n_equations - 1))
691 				i = 0;
692 			else
693 				i++;
694 		} else {
695 			if (i <= 0)
696 				i = n_equations - 1;
697 			else
698 				i--;
699 		}
700 		if (var_in_equation(i, v)) {
701 			cur_equation = i;
702 			return true;
703 		}
704 	}
705 	return false;
706 }
707 
708 /*
709  * Replace all occurrences of variable from_v with to_v in an equation space.
710  */
711 void
rename_var_in_es(en,from_v,to_v)712 rename_var_in_es(en, from_v, to_v)
713 int	en;	 	/* equation space number */
714 long	from_v, to_v;	/* Mathomatic variables */
715 {
716 	int	i;
717 
718 	if (empty_equation_space(en)) {
719 #if	DEBUG
720 		error_bug("Invalid or empty equation number given to rename_var_in_es().");
721 #else
722 		return;
723 #endif
724 	}
725 	for (i = 0; i < n_lhs[en]; i += 2)
726 		if (lhs[en][i].kind == VARIABLE
727 		    && lhs[en][i].token.variable == from_v)
728 			lhs[en][i].token.variable = to_v;
729 	for (i = 0; i < n_rhs[en]; i += 2)
730 		if (rhs[en][i].kind == VARIABLE
731 		    && rhs[en][i].token.variable == from_v)
732 			rhs[en][i].token.variable = to_v;
733 }
734 
735 /*
736  * Substitute every instance of "v" in "equation" with "expression".
737  *
738  * Return true if something was substituted.
739  */
740 int
subst_var_with_exp(equation,np,expression,len,v)741 subst_var_with_exp(equation, np, expression, len, v)
742 token_type	*equation;	/* equation side pointer */
743 int		*np;		/* pointer to equation side length */
744 token_type	*expression;	/* expression pointer */
745 int		len;		/* expression length */
746 long		v;		/* variable to substitute with expression */
747 {
748 	int	j, k;
749 	int	level;
750 	int	substituted = false;
751 
752 	if (v == 0 || len <= 0)
753 		return false;
754 	for (j = *np - 1; j >= 0; j--) {
755 		if (equation[j].kind == VARIABLE && equation[j].token.variable == v) {
756 			level = equation[j].level;
757 			if (*np + len - 1 > n_tokens) {
758 				error_huge();
759 			}
760 			if (len > 1) {
761 				blt(&equation[j+len], &equation[j+1], (*np - (j + 1)) * sizeof(token_type));
762 				*np += len - 1;
763 			}
764 			blt(&equation[j], expression, len * sizeof(token_type));
765 			for (k = j; k < j + len; k++)
766 				equation[k].level += level;
767 			substituted = true;
768 		}
769 	}
770 	if (substituted) {
771 		if (is_integer_var(v) && !is_integer_expr(expression, len)) {
772 			warning(_("Substituting integer variable with non-integer expression."));
773 		}
774 	}
775 	return substituted;
776 }
777 
778 /*
779  * Return the base (minimum) parentheses level encountered in a Mathomatic "expression".
780  */
781 int
min_level(expression,n)782 min_level(expression, n)
783 token_type	*expression;	/* expression pointer */
784 int		n;		/* expression length */
785 {
786 	int		min1;
787 	token_type	*p1, *ep;
788 
789 #if	DEBUG
790 	if (expression == NULL)
791 		error_bug("NULL pointer passed to min_level().");
792 #endif
793 	switch (n) {
794 	case 1:
795 		return expression[0].level;
796 	case 3:
797 		return expression[1].level;
798 	default:
799 		if (n <= 0 || (n & 1) != 1)
800 			error_bug("Invalid expression length in call to min_level().");
801 		break;
802 	}
803 	min1 = expression[1].level;
804 	ep = &expression[n];
805 	for (p1 = &expression[3]; p1 < ep; p1 += 2) {
806 		if (p1->level < min1)
807 			min1 = p1->level;
808 	}
809 	return min1;
810 }
811 
812 /*
813  * Get default equation number from a command parameter string.
814  * The equation number must be the only parameter.
815  * If no equation number is specified, default to the current equation.
816  *
817  * Return -1 on error.
818  */
819 int
get_default_en(cp)820 get_default_en(cp)
821 char	*cp;
822 {
823 	int	i;
824 
825 	if (*cp == '\0') {
826 		i = cur_equation;
827 	} else {
828 		i = decstrtol(cp, &cp) - 1;
829 		if (extra_characters(cp))
830 			return -1;
831 	}
832 	if (not_defined(i)) {
833 		return -1;
834 	}
835 	return i;
836 }
837 
838 /*
839  * Get an expression from the user.
840  * The prompt must be previously copied into the global prompt_str[].
841  *
842  * Return true if successful.
843  */
844 int
get_expr(equation,np)845 get_expr(equation, np)
846 token_type	*equation;	/* where the parsed expression is stored (equation side) */
847 int		*np;		/* pointer to the returned parsed expression length */
848 {
849 	char	buf[DEFAULT_N_TOKENS];
850 	char	*cp;
851 
852 #if	LIBRARY
853 	snprintf(buf, sizeof(buf), "#%+d", pull_number);
854 	pull_number++;
855 	cp = parse_expr(equation, np, buf, true);
856 	if (extra_characters(cp))
857 		return false;
858 	return(cp && *np > 0);
859 #else
860 	for (;;) {
861 		if ((cp = get_string(buf, sizeof(buf))) == NULL) {
862 			return false;
863 		}
864 		cp = parse_expr(equation, np, cp, true);
865 		if (cp && !extra_characters(cp)) {
866 			break;
867 		}
868 	}
869 	return(*np > 0);
870 #endif
871 }
872 
873 /*
874  * Prompt for a variable name from the user.
875  *
876  * Return true if successful.
877  */
878 int
prompt_var(vp)879 prompt_var(vp)
880 long	*vp;	/* pointer to the returned variable */
881 {
882 	char	buf[MAX_CMD_LEN];
883 	char	*cp;
884 
885 	for (;;) {
886 		my_strlcpy(prompt_str, _("Enter variable: "), sizeof(prompt_str));
887 		if ((cp = get_string(buf, sizeof(buf))) == NULL) {
888 			return false;
889 		}
890 		if (*cp == '\0') {
891 			return false;
892 		}
893 		cp = parse_var2(vp, cp);
894 		if (cp == NULL || extra_characters(cp)) {
895 			continue;
896 		}
897 		return true;
898 	}
899 }
900 
901 /*
902  * Return true and display a message if equation "i" is undefined.
903  */
904 int
not_defined(i)905 not_defined(i)
906 int	i;	/* equation space number */
907 {
908 	if (i < 0 || i >= n_equations) {
909 		error(_("Invalid equation number."));
910 		return true;
911 	}
912 	if (n_lhs[i] <= 0) {
913 		if (i == cur_equation) {
914 			error(_("Current equation space is empty."));
915 		} else {
916 			error(_("Equation space is empty."));
917 		}
918 		return true;
919 	}
920 	return false;
921 }
922 
923 /*
924  * Verify that a current equation or expression is loaded.
925  *
926  * Return true and display a message if the current equation space is empty or not defined.
927  */
928 int
current_not_defined(void)929 current_not_defined(void)
930 {
931 	int	i;
932 
933 	i = cur_equation;
934 	if (i < 0 || i >= n_equations) {
935 		error(_("Current equation number out of range; reset to 1."));
936 		i = cur_equation = 0;
937 	}
938 	if (n_lhs[i] <= 0) {
939 		error(_("No current equation or expression."));
940 		return true;
941 	}
942 	return false;
943 }
944 
945 /*
946  * Common routine to output the prompt in prompt_str[] and get a line of input from stdin.
947  * All Mathomatic input comes from this routine, except for file reading.
948  * If there is no more input (EOF), exit this program with no error.
949  *
950  * Returns "string" if successful or NULL on error.
951  */
952 char *
get_string(string,n)953 get_string(string, n)
954 char	*string;	/* storage for input string */
955 int	n;		/* maximum size of "string" in bytes */
956 {
957 #if	LIBRARY
958 	error(_("Library usage error. Input requested, possibly due to missing command-line argument."));
959 	return NULL;
960 #else
961 	int	i;
962 #if	READLINE || EDITLINE
963 	char	*cp;
964 #endif
965 
966 #if	DEBUG
967 	if (string == NULL)
968 		error_bug("NULL pointer passed to get_string().");
969 #endif
970 	if (quiet_mode) {
971 		prompt_str[0] = '\0';	/* don't display a prompt */
972 	}
973 	input_column = strlen(prompt_str);
974 	fflush(NULL);	/* flush everything before gathering input */
975 #if	READLINE || EDITLINE
976 	if (readline_enabled) {
977 		cp = readline(prompt_str);
978 		if (cp == NULL) {
979 			if (!quiet_mode)
980 				printf(_("\nEnd of input.\n"));
981 			exit_program(0);
982 		}
983 		my_strlcpy(string, cp, n);
984 		if (skip_space(cp)[0] && (last_history_string == NULL || strcmp(last_history_string, cp))) {
985 			add_history(cp);
986 			last_history_string = cp;
987 		} else {
988 			free(cp);
989 		}
990 	} else {
991 		printf("%s", prompt_str);
992 		fflush(stdout);
993 		if (fgets(string, n, stdin) == NULL) {
994 			if (!quiet_mode)
995 				printf(_("\nEnd of input.\n"));
996 			exit_program(0);
997 		}
998 	}
999 #else
1000 	printf("%s", prompt_str);
1001 	fflush(stdout);
1002 	if (fgets(string, n, stdin) == NULL) {
1003 		if (!quiet_mode)
1004 			printf(_("\nEnd of input.\n"));
1005 		exit_program(0);
1006 	}
1007 #endif
1008 	if (abort_flag) {
1009 		abort_flag = false;
1010 		longjmp(jmp_save, 13);
1011 	}
1012 	/* Fix an fgets() peculiarity: */
1013 	i = strlen(string) - 1;
1014 	if (i >= 0 && string[i] == '\n') {
1015 		string[i] = '\0';
1016 	}
1017 	if ((gfp != stdout && gfp != stderr) || (echo_input && !quiet_mode)) {
1018 		/* Input that is prompted for is now included in the redirected output
1019 		   of a command to a file, making redirection like logging. */
1020 		fprintf(gfp, "%s%s\n", prompt_str, string);
1021 	}
1022 	set_error_level(string);
1023 	abort_flag = false;
1024 	return string;
1025 #endif
1026 }
1027 
1028 /*
1029  * Display the prompt in prompt_str[] and wait for "y" or "n" followed by Enter.
1030  *
1031  * Return true if "y".
1032  * Return false if "n" or not interactive.
1033  */
1034 int
get_yes_no(void)1035 get_yes_no(void)
1036 {
1037 	char	*cp;
1038 	char	buf[MAX_CMD_LEN];
1039 
1040 #if	0
1041 	if (!isatty(0)) {
1042 		return false;
1043 	}
1044 #endif
1045 	for (;;) {
1046 		if ((cp = get_string(buf, sizeof(buf))) == NULL) {
1047 			return false;
1048 		}
1049 		str_tolower(cp);
1050 		switch (*cp) {
1051 		case 'n':
1052 			return false;
1053 		case 'y':
1054 			return true;
1055 		}
1056 	}
1057 }
1058 
1059 /*
1060  * Display the result of a command,
1061  * or store the pointer to the text of the listed expression
1062  * into result_str if compiled for the library.
1063  *
1064  * Return true if successful.
1065  */
1066 int
return_result(en)1067 return_result(en)
1068 int	en;	/* equation space number the result is in */
1069 {
1070 	if (empty_equation_space(en)) {
1071 		return false;
1072 	}
1073 #if	LIBRARY
1074 	make_fractions_and_group(en);
1075 	if (factor_int_flag) {
1076 		factor_int_equation(en);
1077 	}
1078 	free_result_str();
1079 #if	1	/* Set this to 1 to allow display2d to decide library output mode. */
1080 	if (display2d) {
1081 		result_str = flist_equation_string(en);
1082 		if (result_str == NULL)
1083 			result_str = list_equation(en, false);
1084 	} else {
1085 		result_str = list_equation(en, false);
1086 	}
1087 #else	/* For feeding command output to the next command's input only. */
1088 	result_str = list_equation(en, false);
1089 #endif
1090 	result_en = en;
1091 	if (gfp == stdout) {
1092 		return(result_str != NULL);
1093 	}
1094 #endif
1095 	return(list_sub(en) != 0);
1096 }
1097 
1098 /*
1099  * Free any malloc()ed result_str, so there won't be a memory leak
1100  * in the symbolic math library.
1101  */
1102 void
free_result_str(void)1103 free_result_str(void)
1104 {
1105 	if (result_str) {
1106 		free(result_str);
1107 		result_str = NULL;
1108 	}
1109 	result_en = -1;
1110 }
1111 
1112 /*
1113  * Return true if the first word in the passed string is "all".
1114  */
1115 int
is_all(cp)1116 is_all(cp)
1117 char	*cp;
1118 {
1119 	return(strcmp_tospace(cp, "all") == 0);
1120 }
1121 
1122 /*
1123  * Process an equation number range given in text string "*cpp".
1124  * Skip past all spaces and update "*cpp" to point to the next argument if successful.
1125  * If no equation number or range is given, or it is invalid, assume the current equation is wanted and
1126  * don't skip anything.
1127  *
1128  * Return true if successful,
1129  * with the starting equation number in "*ip"
1130  * and ending equation number in "*jp".
1131  */
1132 int
get_range(cpp,ip,jp)1133 get_range(cpp, ip, jp)
1134 char	**cpp;
1135 int	*ip, *jp;
1136 {
1137 	int	i;
1138 	char	*cp;
1139 	int	rv;
1140 
1141 	cp = skip_comma_space(*cpp);
1142 	if (is_all(cp)) {
1143 		cp = skip_param(cp);
1144 		*ip = 0;
1145 		*jp = n_equations - 1;
1146 		while (*jp > 0 && n_lhs[*jp] == 0)
1147 			(*jp)--;
1148 	} else {
1149 		if (*cp == '0') {
1150 			goto use_current;
1151 		}
1152 		if (isdigit(*cp)) {
1153 			*ip = strtol(cp, &cp, 10) - 1;
1154 		} else {
1155 			*ip = cur_equation;
1156 		}
1157 		if (*cp != '-') {
1158 			if (*cp == '\0' || *cp == ',' || isspace(*cp)) {
1159 				if (not_defined(*ip)) {
1160 					return false;
1161 				}
1162 				*jp = *ip;
1163 				*cpp = skip_comma_space(cp);
1164 				return true;
1165 			} else {
1166 use_current:
1167 				*jp = *ip = cur_equation;
1168 #if	1
1169 				rv = !empty_equation_space(cur_equation);	/* don't display error message */
1170 				if (rv) {
1171 					debug_string(1, _("Defaulting to the current equation space."));
1172 				} else {
1173 					debug_string(1, _("Defaulting to current empty equation space."));
1174 				}
1175 #else
1176 				rv = !current_not_defined();		/* display an error message if error */
1177 				if (rv) {
1178 					debug_string(1, _("Defaulting to the current equation space."));
1179 				}
1180 #endif
1181 				return rv;
1182 			}
1183 		}
1184 		(cp)++;
1185 		if (*cp == '0') {
1186 			goto use_current;
1187 		}
1188 		if (isdigit(*cp)) {
1189 			*jp = strtol(cp, &cp, 10) - 1;
1190 		} else {
1191 			*jp = cur_equation;
1192 		}
1193 		if (*cp && !isspace(*cp)) {
1194 			goto use_current;
1195 		}
1196 		if (*ip < 0 || *ip >= N_EQUATIONS || *jp < 0 || *jp >= N_EQUATIONS) {
1197 			error(_("Invalid equation number (out of range)."));
1198 			return false;
1199 		}
1200 		if (*jp < *ip) {
1201 			i = *ip;
1202 			*ip = *jp;
1203 			*jp = i;
1204 		}
1205 	}
1206 	cp = skip_comma_space(cp);
1207 	for (i = *ip; i <= *jp; i++) {
1208 		if (n_lhs[i] > 0) {
1209 			*cpp = cp;
1210 			return true;
1211 		}
1212 	}
1213 	error(_("No expressions defined in specified range."));
1214 	return false;
1215 }
1216 
1217 /*
1218  * This function is provided to make sure there is nothing else on a command line.
1219  *
1220  * Returns true if any non-space characters are encountered before the end of the string
1221  * and an error message is printed.
1222  * Otherwise just returns false indicating everything is OK.
1223  */
1224 int
extra_characters(cp)1225 extra_characters(cp)
1226 char	*cp;	/* command line string */
1227 {
1228 	if (cp) {
1229 		cp = skip_comma_space(cp);
1230 		if (*cp) {
1231 			printf(_("\nError: \"%s\" not required on input line.\n"), cp);
1232 			error(_("Extra characters or unrecognized argument."));
1233 			return true;
1234 		}
1235 	}
1236 	return false;
1237 }
1238 
1239 /*
1240  * get_range() if it is the last possible option on the command line,
1241  * otherwise display an error message and return false.
1242  */
1243 int
get_range_eol(cpp,ip,jp)1244 get_range_eol(cpp, ip, jp)
1245 char	**cpp;
1246 int	*ip, *jp;
1247 {
1248 	if (!get_range(cpp, ip, jp)) {
1249 		return false;
1250 	}
1251 	if (extra_characters(*cpp)) {
1252 		return false;
1253 	}
1254 	return true;
1255 }
1256 
1257 /*
1258  * Skip over space characters.
1259  */
1260 char *
skip_space(cp)1261 skip_space(cp)
1262 char	*cp;	/* character pointer */
1263 {
1264 	if (cp) {
1265 		while (*cp && isspace(*cp))
1266 			cp++;
1267 	}
1268 	return cp;
1269 }
1270 
1271 /*
1272  * Skip over a possible comma and space characters.
1273  */
1274 char *
skip_comma_space(cp)1275 skip_comma_space(cp)
1276 char	*cp;	/* character pointer */
1277 {
1278 	if (cp) {
1279 		cp = skip_space(cp);
1280 		if (*cp == ',')
1281 			cp = skip_space(cp + 1);
1282 	}
1283 	return cp;
1284 }
1285 
1286 /*
1287  * Enhanced decimal strtol().
1288  * Skips trailing spaces or commas.
1289  */
1290 long
decstrtol(cp,cpp)1291 decstrtol(cp, cpp)
1292 char	*cp, **cpp;
1293 {
1294 	long	l;
1295 
1296 	l = strtol(cp, cpp, 10);
1297 	if (cpp && *cpp && cp != *cpp) {
1298 		*cpp = skip_comma_space(*cpp);
1299 	}
1300 	return l;
1301 }
1302 
1303 /*
1304  * Return true if passed character is a Mathomatic command parameter delimiter.
1305  */
1306 int
isdelimiter(ch)1307 isdelimiter(ch)
1308 int	ch;
1309 {
1310 	return(isspace(ch) || ch == ',' || ch == '=');
1311 }
1312 
1313 /*
1314  * Skip over the current parameter in a Mathomatic command line string.
1315  * Parameters are usually separated with spaces or a comma or equals sign.
1316  *
1317  * Returns a string (character pointer) to the next parameter.
1318  */
1319 char *
skip_param(cp)1320 skip_param(cp)
1321 char	*cp;
1322 {
1323 	if (cp) {
1324 		while (*cp && (!isascii(*cp) || !isdelimiter(*cp))) {
1325 			cp++;
1326 		}
1327 		cp = skip_space(cp);
1328 		if (*cp && isdelimiter(*cp)) {
1329 			cp = skip_space(cp + 1);
1330 		}
1331 	}
1332 	return(cp);
1333 }
1334 
1335 /*
1336  * Compare strings up to the end or the first space or parameter delimiter,
1337  * ignoring alphabetic case.
1338  *
1339  * Returns zero on exact match, otherwise non-zero if strings are different.
1340  */
1341 int
strcmp_tospace(cp1,cp2)1342 strcmp_tospace(cp1, cp2)
1343 char	*cp1, *cp2;
1344 {
1345 	char	*cp1a, *cp2a;
1346 
1347 #if	DEBUG
1348 	if (cp1 == NULL || cp2 == NULL)
1349 		error_bug("NULL pointer passed to strcmp_tospace().");
1350 #endif
1351 	for (cp1a = cp1; *cp1a && !isdelimiter(*cp1a); cp1a++)
1352 		;
1353 	for (cp2a = cp2; *cp2a && !isdelimiter(*cp2a); cp2a++)
1354 		;
1355 	return strncasecmp(cp1, cp2, max(cp1a - cp1, cp2a - cp2));
1356 }
1357 
1358 /*
1359  * Return the number of "level" additive type operators.
1360  */
1361 int
level_plus_count(p1,n1,level)1362 level_plus_count(p1, n1, level)
1363 token_type	*p1;	/* expression pointer */
1364 int		n1;	/* expression length */
1365 int		level;	/* parentheses level number to check */
1366 {
1367 	int	i;
1368 	int	count = 0;
1369 
1370 	for (i = 1; i < n1; i += 2) {
1371 		if (p1[i].level == level) {
1372 			switch (p1[i].token.operatr) {
1373 			case PLUS:
1374 			case MINUS:
1375 				count++;
1376 			}
1377 		}
1378 	}
1379 	return count;
1380 }
1381 
1382 /*
1383  * Return the number of level 1 additive type operators.
1384  */
1385 int
level1_plus_count(p1,n1)1386 level1_plus_count(p1, n1)
1387 token_type	*p1;	/* expression pointer */
1388 int		n1;	/* expression length */
1389 {
1390 	return level_plus_count(p1, n1, min_level(p1, n1));
1391 }
1392 
1393 /*
1394  * Return the count of variables in an expression.
1395  */
1396 int
var_count(p1,n1)1397 var_count(p1, n1)
1398 token_type	*p1;	/* expression pointer */
1399 int		n1;	/* expression length */
1400 {
1401 	int	i;
1402 	int	count = 0;
1403 
1404 	for (i = 0; i < n1; i += 2) {
1405 		if (p1[i].kind == VARIABLE) {
1406 			count++;
1407 		}
1408 	}
1409 	return count;
1410 }
1411 
1412 /*
1413  * Set "*vp" if single variable expression.
1414  *
1415  * Return true if expression contains no variables.
1416  */
1417 int
no_vars(source,n,vp)1418 no_vars(source, n, vp)
1419 token_type	*source;	/* expression pointer */
1420 int		n;		/* expression length */
1421 long		*vp;		/* variable pointer */
1422 {
1423 	int	j;
1424 	int	found = false;
1425 
1426 	if (*vp) {
1427 		return(var_count(source, n) == 0);
1428 	}
1429 	for (j = 0; j < n; j += 2) {
1430 		if (source[j].kind == VARIABLE) {
1431 			if ((source[j].token.variable & VAR_MASK) <= SIGN)
1432 				continue;
1433 			if (*vp) {
1434 				if (*vp != source[j].token.variable) {
1435 					*vp = 0;
1436 					break;
1437 				}
1438 			} else {
1439 				found = true;
1440 				*vp = source[j].token.variable;
1441 			}
1442 		}
1443 	}
1444 	return(!found);
1445 }
1446 
1447 /*
1448  * Return true if expression contains infinity or NaN (Not a Number).
1449  */
1450 int
exp_contains_infinity(p1,n1)1451 exp_contains_infinity(p1, n1)
1452 token_type	*p1;	/* expression pointer */
1453 int		n1;	/* expression length */
1454 {
1455 	int	i;
1456 
1457 	for (i = 0; i < n1; i++) {
1458 		if (p1[i].kind == CONSTANT && !isfinite(p1[i].token.constant)) {
1459 			return true;
1460 		}
1461 	}
1462 	return false;
1463 }
1464 
1465 /*
1466  * Return true if expression contains NaN (Not a Number).
1467  */
1468 int
exp_contains_nan(p1,n1)1469 exp_contains_nan(p1, n1)
1470 token_type	*p1;	/* expression pointer */
1471 int		n1;	/* expression length */
1472 {
1473 	int	i;
1474 
1475 	for (i = 0; i < n1; i++) {
1476 		if (p1[i].kind == CONSTANT && isnan(p1[i].token.constant)) {
1477 			return true;
1478 		}
1479 	}
1480 	return false;
1481 }
1482 
1483 /*
1484  * Return true if expression is numeric (not symbolic).
1485  * Pseudo-variables e, pi, i, and sign are considered numeric.
1486  */
1487 int
exp_is_numeric(p1,n1)1488 exp_is_numeric(p1, n1)
1489 token_type	*p1;	/* expression pointer */
1490 int		n1;	/* expression length */
1491 {
1492 	int	i;
1493 
1494 	for (i = 0; i < n1; i++) {
1495 		if (p1[i].kind == VARIABLE && (p1[i].token.variable & VAR_MASK) > SIGN) {
1496 			return false;		/* not numerical (contains a variable) */
1497 		}
1498 	}
1499 	return true;
1500 }
1501 
1502 /*
1503  * Test if expression contains an absolute value.
1504  * Return true if it does.
1505  */
1506 int
exp_is_absolute(p1,n1)1507 exp_is_absolute(p1, n1)
1508 token_type	*p1;	/* expression pointer */
1509 int		n1;	/* expression length */
1510 {
1511 	int	i;
1512 	int	level;
1513 
1514 	for (i = n1 - 2; i > 2; i -= 2) {
1515 		if (p1[i].token.operatr != POWER)
1516 			continue;
1517 		level = p1[i].level;
1518 		if (p1[i+1].level == level && p1[i+1].kind == CONSTANT && fmod(p1[i+1].token.constant, 1.0) != 0.0) {
1519 			level++;
1520 			if (p1[i-2].token.operatr == POWER && p1[i-2].level == level && p1[i-1].level == level && p1[i-1].kind == CONSTANT) {
1521 				return true;
1522 			}
1523 		}
1524 	}
1525 	return false;
1526 }
1527 
1528 /*
1529  * Check for division by zero.
1530  *
1531  * Display a warning and return true if passed double is 0.
1532  */
1533 int
check_divide_by_zero(denominator)1534 check_divide_by_zero(denominator)
1535 double	denominator;
1536 {
1537 	if (denominator == 0) {
1538 		warning(_("Division by zero."));
1539 		return true;
1540 	}
1541 	return false;
1542 }
1543 
1544 #if	CYGWIN || MINGW
1545 /*
1546  * dirname(3) function for Microsoft Windows.
1547  * dirname(3) strips the non-directory suffix from a filename.
1548  */
1549 char *
dirname_win(cp)1550 dirname_win(cp)
1551 char	*cp;	/* string containing filename to modify */
1552 {
1553 	int	i;
1554 
1555 	if (cp == NULL)
1556 		return(".");
1557 	i = strlen(cp);
1558 	while (i >= 0 && cp[i] != '\\' && cp[i] != '/')
1559 		i--;
1560 	if (i < 0)
1561 		return(".");
1562 	cp[i] = '\0';
1563 	return(cp);
1564 }
1565 #endif
1566 
1567 #if	!SECURE
1568 /*
1569  * Load set options from startup file "~/.mathomaticrc".
1570  *
1571  * Return false if there was an error reading the startup file,
1572  * otherwise return true.
1573  */
1574 int
load_rc(return_true_if_no_file,ofp)1575 load_rc(return_true_if_no_file, ofp)
1576 int	return_true_if_no_file;
1577 FILE	*ofp;	/* if non-NULL, display each line as read in to this file */
1578 {
1579 	FILE	*fp = NULL;
1580 	char	buf[MAX_CMD_LEN];
1581 	char	*cp;
1582 	int	rv = true;
1583 
1584 	cp = getenv("HOME");
1585 	if (cp) {
1586 		snprintf(rc_file, sizeof(rc_file), "%s/%s", cp, ".mathomaticrc");
1587 		fp = fopen(rc_file, "r");
1588 	}
1589 #if	CYGWIN || MINGW
1590 	if (fp == NULL && cp) {
1591 		snprintf(rc_file, sizeof(rc_file), "%s/%s", cp, "mathomatic.rc");
1592 		fp = fopen(rc_file, "r");
1593 	}
1594 	if (fp == NULL && dir_path) {
1595 		snprintf(rc_file, sizeof(rc_file), "%s/%s", dir_path, "mathomatic.rc");
1596 		fp = fopen(rc_file, "r");
1597 	}
1598 #endif
1599 	if (fp == NULL) {
1600 		if (return_true_if_no_file) {
1601 			return true;
1602 		} else {
1603 			perror(rc_file);
1604 			return false;
1605 		}
1606 	}
1607 	if (!quiet_mode && !eoption) {
1608 		printf(_("Loading startup set options from \"%s\".\n"), rc_file);
1609 	}
1610 	while ((cp = fgets(buf, sizeof(buf), fp)) != NULL) {
1611 		if (ofp)
1612 			fprintf(ofp, "%s", cp);
1613 		set_error_level(cp);
1614 		if (!set_options(cp, true))
1615 			rv = false;
1616 	}
1617 	if (fclose(fp)) {
1618 		rv = false;
1619 		perror(rc_file);
1620 	}
1621 	return rv;
1622 }
1623 
1624 #if	0	/* not currently used */
1625 /*
1626  * Display set options from startup file "~/.mathomaticrc".
1627  *
1628  * Return false if there was an error reading the startup file,
1629  * otherwise return true.
1630  */
1631 int
1632 display_rc(ofp)
1633 FILE	*ofp;
1634 {
1635 	FILE	*fp = NULL;
1636 	char	buf[MAX_CMD_LEN];
1637 	char	*cp;
1638 	int	rv = true;
1639 
1640 	printf(_("Displaying startup set options from \"%s\":\n\n"), rc_file);
1641 	fp = fopen(rc_file, "r");
1642 	if (fp == NULL) {
1643 		perror(rc_file);
1644 		return false;
1645 	}
1646 	while ((cp = fgets(buf, sizeof(buf), fp)) != NULL) {
1647 		fprintf(ofp, "%s", cp);
1648 	}
1649 	if (fclose(fp)) {
1650 		rv = false;
1651 		perror(rc_file);
1652 	}
1653 	return rv;
1654 }
1655 #endif
1656 #endif
1657