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 © 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