/* * Standard routines for Mathomatic. * * Copyright (C) 1987-2012 George Gesslein II. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA The chief copyright holder can be contacted at gesslein@mathomatic.org, or George Gesslein II, P.O. Box 224, Lansing, NY 14882-0224 USA. */ #include "includes.h" #if UNIX || CYGWIN #include #include #endif /* * Display the main Mathomatic startup message. * fp is the output file stream pointer it goes to, and should be stdout or gfp, * but really can go to any file you wish. */ void display_startup_message(fp) FILE *fp; /* output file pointer */ { long es_size; #if SECURE fprintf(fp, _("Secure ")); #else if (security_level >= 2) fprintf(fp, _("Secure ")); else if (security_level == -1) fprintf(fp, "m4 "); #endif fprintf(fp, "Mathomatic version %s\n", VERSION); if (html_flag) fprintf(fp, "Copyright © 1987-2012 George Gesslein II.\n"); else fprintf(fp, "Copyright (C) 1987-2012 George Gesslein II.\n"); es_size = (long) n_tokens * sizeof(token_type) * 2L / 1000L; if (es_size >= 1000) { fprintf(fp, _("%d equation spaces available in RAM; %ld megabytes per equation space.\n"), N_EQUATIONS, (es_size + 500L) / 1000L); } else { fprintf(fp, _("%d equation spaces available in RAM; %ld kilobytes per equation space.\n"), N_EQUATIONS, es_size); } } /* * Standard function to report an error to the user. */ void error(str) const char *str; /* constant string to display */ { error_str = str; /* save reference to str, must be a constant string, temporary strings don't work */ #if !SILENT && !LIBRARY set_color(2); /* set color to red */ printf("%s\n", str); default_color(false); /* restore to default color */ #endif } /* * Reset last call to error(), as if it didn't happen. */ void reset_error(void) { #if !SILENT && !LIBRARY if (error_str) printf(_("Forgetting previous error.\n")); #endif error_str = NULL; } /* * Standard function to report a warning only once to the user. * A warning is less serious than an error. */ void warning(str) const char *str; /* constant string to display */ { int already_warned = false; if (warning_str) { if (strcmp(str, warning_str) == 0) already_warned = true; } warning_str = str; /* save reference to str, must be a constant string, temporary strings don't work */ #if !SILENT && !LIBRARY if (!already_warned && debug_level >= -1) { set_color(1); /* set color to yellow */ printf("Warning: %s\n", str); default_color(false); /* restore to default color */ } #endif } /* * This is called when the maximum expression size has been exceeded. * * There is no return. */ void error_huge(void) { longjmp(jmp_save, 14); } /* * This is called when a bug test result is positive. * * There is no return. */ void error_bug(str) const char *str; /* constant string to display */ { /* Return and display the passed error message in str. */ error(str); /* str must be a constant string, temporary strings don't work */ #if SILENT || LIBRARY printf("%s\n", str); #endif printf(_("Please report this bug to the maintainers,\n")); printf(_("along with the entry sequence that caused it.\n")); #if !LIBRARY printf(_("Type \"help bugs\" for info on how to report bugs found in this program.\n")); #endif longjmp(jmp_save, 13); /* Abort the current operation with the critical error number 13. */ } /* * Check if a floating point math function flagged an error. * * There is no return if an error message is displayed. */ void check_err(void) { switch (errno) { case EDOM: errno = 0; if (domain_check) { domain_check = false; } else { error(_("Domain error in constant.")); longjmp(jmp_save, 2); } break; case ERANGE: errno = 0; error(_("Floating point constant out of range.")); longjmp(jmp_save, 2); break; } } /* * Get the current screen (window) width and height from the operating system. * * Return true if the global integers screen_columns and/or screen_rows were set. */ int get_screen_size(void) { int rv = false; #if UNIX || CYGWIN struct winsize ws; ws.ws_col = 0; ws.ws_row = 0; if (ioctl(1, TIOCGWINSZ, &ws) >= 0) { if (ws.ws_col > 0) { screen_columns = ws.ws_col; rv = true; } if (ws.ws_row > 0) { screen_rows = ws.ws_row; rv = true; } } #else screen_columns = STANDARD_SCREEN_COLUMNS; screen_rows = STANDARD_SCREEN_ROWS; rv = true; #endif return rv; } /* * Allocate the display lines in the vscreen[] array. * Call this before using vscreen[]. * * Return true with vscreen[] allocated to TEXT_ROWS*current_columns characters if successful. */ int malloc_vscreen(void) { int i; if (current_columns == 0 || ((screen_columns > 0) ? (current_columns != screen_columns) : (current_columns != TEXT_COLUMNS))) { if (screen_columns > 0) { current_columns = screen_columns; } else { current_columns = TEXT_COLUMNS; } for (i = 0; i < TEXT_ROWS; i++) { if (vscreen[i]) { free(vscreen[i]); } vscreen[i] = malloc(current_columns + 1); if (vscreen[i] == NULL) { error(_("Out of memory (can't malloc(3)).")); current_columns = 0; return false; } } } return true; } /* * Allocate the needed global expression storage arrays. * Each is static and can hold n_tokens elements. * n_tokens must not change until Mathomatic terminates. * * init_mem() is called only once upon Mathomatic startup * before using the symbolic math engine, * and can be undone by calling free_mem() below. * * Returns true if successful, otherwise Mathomatic cannot be used. */ int init_mem(void) { if (n_tokens <= 0) return false; if ((scratch = (token_type *) malloc(((n_tokens * 3) / 2) * sizeof(token_type))) == NULL || (tes = (token_type *) malloc(n_tokens * sizeof(token_type))) == NULL || (tlhs = (token_type *) malloc(n_tokens * sizeof(token_type))) == NULL || (trhs = (token_type *) malloc(n_tokens * sizeof(token_type))) == NULL) { return false; } if (alloc_next_espace() < 0) { /* make sure there is at least 1 equation space */ return false; } clear_all(); return true; } #if LIBRARY || VALGRIND /* * Free the global expression storage arrays and other known memory buffers. * After calling this, memory usage is reset and Mathomatic becomes unusable, * so do not call unless finished with using the Mathomatic code. * * This routine is usually not needed, because when a program exits, * all the memory it allocated is released by the operating system. * Inclusion of this routine was requested by Tam Hanna for use with Symbian OS. */ void free_mem(void) { int i; clear_all(); free(scratch); free(tes); free(tlhs); free(trhs); for (i = 0; i < N_EQUATIONS; i++) { if (lhs[i]) { free(lhs[i]); lhs[i] = NULL; } if (rhs[i]) { free(rhs[i]); rhs[i] = NULL; } } n_equations = 0; for (i = 0; i < TEXT_ROWS; i++) { if (vscreen[i]) { free(vscreen[i]); vscreen[i] = NULL; } } current_columns = 0; } #endif #if DEBUG /* * Use this function to check for any erroneous global conditions. * Use of this function is rather paranoid, but helpful. * * Always returns true, or doesn't return on error. */ int check_gvars(void) { if (!(domain_check == false && high_prec == false && partial_flag == true && symb_flag == false && sign_cmp_flag == false && approximate_roots == false)) error_bug("Global vars got changed!"); if (!(zero_token.level == 1 && zero_token.kind == CONSTANT && zero_token.token.constant == 0.0 && one_token.level == 1 && one_token.kind == CONSTANT && one_token.token.constant == 1.0)) error_bug("Global constants got changed!"); return true; } #endif /* * Initialize some important global variables to their defaults. * This is called on startup and by process() to reset the global flags to the default state. * This is also called when processing is aborted with a longjmp(3). */ void init_gvars(void) { domain_check = false; high_prec = false; partial_flag = true; symb_flag = false; sign_cmp_flag = false; approximate_roots = false; repeat_flag = false; /* initialize the universal and often used constant "0" expression */ zero_token.level = 1; zero_token.kind = CONSTANT; zero_token.token.constant = 0.0; /* initialize the universal and often used constant "1" expression */ one_token.level = 1; one_token.kind = CONSTANT; one_token.token.constant = 1.0; } /* * Clean up when processing is unexpectedly interrupted or terminated. */ void clean_up(void) { int i; init_gvars(); /* reset the global variables to the default */ if (gfp != default_out) { /* reset the output file to default */ #if !SECURE if (gfp != stdout && gfp != stderr) fclose(gfp); #endif gfp = default_out; } gfp_filename = NULL; for (i = 0; i < n_equations; i++) { if (n_lhs[i] <= 0) { n_lhs[i] = 0; n_rhs[i] = 0; } } } /* * Register all sign variables in all equation spaces * so that the next sign variables returned by next_sign() will be unique. */ void set_sign_array(void) { int i, j; CLEAR_ARRAY(sign_array); for (i = 0; i < n_equations; i++) { if (n_lhs[i] > 0) { for (j = 0; j < n_lhs[i]; j += 2) { if (lhs[i][j].kind == VARIABLE && (lhs[i][j].token.variable & VAR_MASK) == SIGN) { sign_array[(lhs[i][j].token.variable >> VAR_SHIFT) & SUBSCRIPT_MASK] = true; } } for (j = 0; j < n_rhs[i]; j += 2) { if (rhs[i][j].kind == VARIABLE && (rhs[i][j].token.variable & VAR_MASK) == SIGN) { sign_array[(rhs[i][j].token.variable >> VAR_SHIFT) & SUBSCRIPT_MASK] = true; } } } } } /* * Return next unused sign variable in "*vp". * Mark it used. */ int next_sign(vp) long *vp; { int i; for (i = 0;; i++) { if (i >= ARR_CNT(sign_array)) { /* out of unique sign variables */ *vp = SIGN; return false; } if (!sign_array[i]) { *vp = SIGN + (((long) i) << VAR_SHIFT); sign_array[i] = true; break; } } return true; } /* * Erase all equation spaces and initialize the global variables. * Similar to a restart. */ void clear_all(void) { int i; /* select first equation space */ cur_equation = 0; /* erase all equation spaces by setting their length to zero */ CLEAR_ARRAY(n_lhs); CLEAR_ARRAY(n_rhs); /* forget all variables names */ for (i = 0; var_names[i]; i++) { free(var_names[i]); var_names[i] = NULL; } /* reset everything to a known state */ CLEAR_ARRAY(sign_array); init_gvars(); } /* * Return true if the specified equation space is available, * zeroing and allocating if necessary. */ int alloc_espace(i) int i; /* equation space number */ { if (i < 0 || i >= N_EQUATIONS) return false; n_lhs[i] = 0; n_rhs[i] = 0; if (lhs[i] && rhs[i]) return true; /* already allocated */ if (lhs[i] || rhs[i]) return false; /* something is wrong */ lhs[i] = (token_type *) malloc(n_tokens * sizeof(token_type)); if (lhs[i] == NULL) return false; rhs[i] = (token_type *) malloc(n_tokens * sizeof(token_type)); if (rhs[i] == NULL) { free(lhs[i]); lhs[i] = NULL; return false; } return true; } /* * Allocate all equation spaces up to and including an equation space number, * making sure the specified equation number is valid and usable. * * Returns true if successful. */ int alloc_to_espace(en) int en; /* equation space number */ { if (en < 0 || en >= N_EQUATIONS) return false; for (;;) { if (en < n_equations) return true; if (n_equations >= N_EQUATIONS) return false; if (!alloc_espace(n_equations)) { warning(_("Memory is exhausted.")); return false; } n_equations++; } } /* * Allocate or reuse an empty equation space. * * Returns empty equation space number ready for use or -1 on error. */ int alloc_next_espace(void) { int i, n; for (n = cur_equation, i = 0;; n = (n + 1) % N_EQUATIONS, i++) { if (i >= N_EQUATIONS) return -1; if (n >= n_equations) { n = n_equations; if (!alloc_espace(n)) { warning(_("Memory is exhausted.")); for (n = 0; n < n_equations; n++) { if (n_lhs[n] == 0) { n_rhs[n] = 0; return n; } } return -1; } n_equations++; return n; } if (n_lhs[n] == 0) break; } n_rhs[n] = 0; return n; } /* * Return the number of the next empty equation space, otherwise don't return. */ int next_espace(void) { int i, j; long answer_v = 0; /* Mathomatic answer variable */ i = alloc_next_espace(); if (i < 0) { #if !SILENT printf(_("Deleting old numeric calculations to free up equation spaces.\n")); #endif parse_var(&answer_v, "answer"); /* convert to a Mathomatic variable */ for (j = 0; j < n_equations; j++) { if (n_lhs[j] == 1 && lhs[j][0].kind == VARIABLE && lhs[j][0].token.variable == answer_v) { /* delete calculation from memory */ n_lhs[j] = 0; n_rhs[j] = 0; } } i = alloc_next_espace(); if (i < 0) { error(_("Out of free equation spaces.")); #if !SILENT printf(_("Use the clear command on unnecessary equations and try again.\n")); #endif longjmp(jmp_save, 3); /* do not return */ } } return i; } /* * Copy equation space "src" to equation space "dest". * "dest" is overwritten. */ void copy_espace(src, dest) int src, dest; /* equation space numbers */ { if (src == dest) { #if DEBUG error_bug("Internal error: copy_espace() source and destination the same."); #endif return; } blt(lhs[dest], lhs[src], n_lhs[src] * sizeof(token_type)); n_lhs[dest] = n_lhs[src]; blt(rhs[dest], rhs[src], n_rhs[src] * sizeof(token_type)); n_rhs[dest] = n_rhs[src]; } /* * Return true if equation space "i" is a valid equation solved for a normal variable. */ int solved_equation(i) int i; { if (empty_equation_space(i)) return false; if (n_rhs[i] <= 0) return false; if (n_lhs[i] != 1 || lhs[i][0].kind != VARIABLE || (lhs[i][0].token.variable & VAR_MASK) <= SIGN) return false; if (found_var(rhs[i], n_rhs[i], lhs[i][0].token.variable)) return false; return true; } /* * Return the number of times variable "v" is found in an expression. */ int found_var(p1, n, v) token_type *p1; /* expression pointer */ int n; /* expression length */ long v; /* standard Mathomatic variable */ { int j; int count = 0; if (v) { for (j = 0; j < n; j++) { if (p1[j].kind == VARIABLE && p1[j].token.variable == v) { count++; } } } return count; } /* * Return true if variable "v" exists in equation space "i". */ int var_in_equation(i, v) int i; /* equation space number */ long v; /* standard Mathomatic variable */ { if (empty_equation_space(i)) return false; if (found_var(lhs[i], n_lhs[i], v)) return true; if (n_rhs[i] <= 0) return false; if (found_var(rhs[i], n_rhs[i], v)) return true; return false; } /* * Return true if variable "v" exists in any equation space. * * Search forward starting at the next equation space if forward_direction is true, * otherwise search backwards starting at the previous equation space. * If found, return true with cur_equation set to the equation space the variable is found in. */ int search_all_for_var(v, forward_direction) long v; int forward_direction; { int i, n; i = cur_equation; for (n = 0; n < n_equations; n++) { if (forward_direction) { if (i >= (n_equations - 1)) i = 0; else i++; } else { if (i <= 0) i = n_equations - 1; else i--; } if (var_in_equation(i, v)) { cur_equation = i; return true; } } return false; } /* * Replace all occurrences of variable from_v with to_v in an equation space. */ void rename_var_in_es(en, from_v, to_v) int en; /* equation space number */ long from_v, to_v; /* Mathomatic variables */ { int i; if (empty_equation_space(en)) { #if DEBUG error_bug("Invalid or empty equation number given to rename_var_in_es()."); #else return; #endif } for (i = 0; i < n_lhs[en]; i += 2) if (lhs[en][i].kind == VARIABLE && lhs[en][i].token.variable == from_v) lhs[en][i].token.variable = to_v; for (i = 0; i < n_rhs[en]; i += 2) if (rhs[en][i].kind == VARIABLE && rhs[en][i].token.variable == from_v) rhs[en][i].token.variable = to_v; } /* * Substitute every instance of "v" in "equation" with "expression". * * Return true if something was substituted. */ int subst_var_with_exp(equation, np, expression, len, v) token_type *equation; /* equation side pointer */ int *np; /* pointer to equation side length */ token_type *expression; /* expression pointer */ int len; /* expression length */ long v; /* variable to substitute with expression */ { int j, k; int level; int substituted = false; if (v == 0 || len <= 0) return false; for (j = *np - 1; j >= 0; j--) { if (equation[j].kind == VARIABLE && equation[j].token.variable == v) { level = equation[j].level; if (*np + len - 1 > n_tokens) { error_huge(); } if (len > 1) { blt(&equation[j+len], &equation[j+1], (*np - (j + 1)) * sizeof(token_type)); *np += len - 1; } blt(&equation[j], expression, len * sizeof(token_type)); for (k = j; k < j + len; k++) equation[k].level += level; substituted = true; } } if (substituted) { if (is_integer_var(v) && !is_integer_expr(expression, len)) { warning(_("Substituting integer variable with non-integer expression.")); } } return substituted; } /* * Return the base (minimum) parentheses level encountered in a Mathomatic "expression". */ int min_level(expression, n) token_type *expression; /* expression pointer */ int n; /* expression length */ { int min1; token_type *p1, *ep; #if DEBUG if (expression == NULL) error_bug("NULL pointer passed to min_level()."); #endif switch (n) { case 1: return expression[0].level; case 3: return expression[1].level; default: if (n <= 0 || (n & 1) != 1) error_bug("Invalid expression length in call to min_level()."); break; } min1 = expression[1].level; ep = &expression[n]; for (p1 = &expression[3]; p1 < ep; p1 += 2) { if (p1->level < min1) min1 = p1->level; } return min1; } /* * Get default equation number from a command parameter string. * The equation number must be the only parameter. * If no equation number is specified, default to the current equation. * * Return -1 on error. */ int get_default_en(cp) char *cp; { int i; if (*cp == '\0') { i = cur_equation; } else { i = decstrtol(cp, &cp) - 1; if (extra_characters(cp)) return -1; } if (not_defined(i)) { return -1; } return i; } /* * Get an expression from the user. * The prompt must be previously copied into the global prompt_str[]. * * Return true if successful. */ int get_expr(equation, np) token_type *equation; /* where the parsed expression is stored (equation side) */ int *np; /* pointer to the returned parsed expression length */ { char buf[DEFAULT_N_TOKENS]; char *cp; #if LIBRARY snprintf(buf, sizeof(buf), "#%+d", pull_number); pull_number++; cp = parse_expr(equation, np, buf, true); if (extra_characters(cp)) return false; return(cp && *np > 0); #else for (;;) { if ((cp = get_string(buf, sizeof(buf))) == NULL) { return false; } cp = parse_expr(equation, np, cp, true); if (cp && !extra_characters(cp)) { break; } } return(*np > 0); #endif } /* * Prompt for a variable name from the user. * * Return true if successful. */ int prompt_var(vp) long *vp; /* pointer to the returned variable */ { char buf[MAX_CMD_LEN]; char *cp; for (;;) { my_strlcpy(prompt_str, _("Enter variable: "), sizeof(prompt_str)); if ((cp = get_string(buf, sizeof(buf))) == NULL) { return false; } if (*cp == '\0') { return false; } cp = parse_var2(vp, cp); if (cp == NULL || extra_characters(cp)) { continue; } return true; } } /* * Return true and display a message if equation "i" is undefined. */ int not_defined(i) int i; /* equation space number */ { if (i < 0 || i >= n_equations) { error(_("Invalid equation number.")); return true; } if (n_lhs[i] <= 0) { if (i == cur_equation) { error(_("Current equation space is empty.")); } else { error(_("Equation space is empty.")); } return true; } return false; } /* * Verify that a current equation or expression is loaded. * * Return true and display a message if the current equation space is empty or not defined. */ int current_not_defined(void) { int i; i = cur_equation; if (i < 0 || i >= n_equations) { error(_("Current equation number out of range; reset to 1.")); i = cur_equation = 0; } if (n_lhs[i] <= 0) { error(_("No current equation or expression.")); return true; } return false; } /* * Common routine to output the prompt in prompt_str[] and get a line of input from stdin. * All Mathomatic input comes from this routine, except for file reading. * If there is no more input (EOF), exit this program with no error. * * Returns "string" if successful or NULL on error. */ char * get_string(string, n) char *string; /* storage for input string */ int n; /* maximum size of "string" in bytes */ { #if LIBRARY error(_("Library usage error. Input requested, possibly due to missing command-line argument.")); return NULL; #else int i; #if READLINE || EDITLINE char *cp; #endif #if DEBUG if (string == NULL) error_bug("NULL pointer passed to get_string()."); #endif if (quiet_mode) { prompt_str[0] = '\0'; /* don't display a prompt */ } input_column = strlen(prompt_str); fflush(NULL); /* flush everything before gathering input */ #if READLINE || EDITLINE if (readline_enabled) { cp = readline(prompt_str); if (cp == NULL) { if (!quiet_mode) printf(_("\nEnd of input.\n")); exit_program(0); } my_strlcpy(string, cp, n); if (skip_space(cp)[0] && (last_history_string == NULL || strcmp(last_history_string, cp))) { add_history(cp); last_history_string = cp; } else { free(cp); } } else { printf("%s", prompt_str); fflush(stdout); if (fgets(string, n, stdin) == NULL) { if (!quiet_mode) printf(_("\nEnd of input.\n")); exit_program(0); } } #else printf("%s", prompt_str); fflush(stdout); if (fgets(string, n, stdin) == NULL) { if (!quiet_mode) printf(_("\nEnd of input.\n")); exit_program(0); } #endif if (abort_flag) { abort_flag = false; longjmp(jmp_save, 13); } /* Fix an fgets() peculiarity: */ i = strlen(string) - 1; if (i >= 0 && string[i] == '\n') { string[i] = '\0'; } if ((gfp != stdout && gfp != stderr) || (echo_input && !quiet_mode)) { /* Input that is prompted for is now included in the redirected output of a command to a file, making redirection like logging. */ fprintf(gfp, "%s%s\n", prompt_str, string); } set_error_level(string); abort_flag = false; return string; #endif } /* * Display the prompt in prompt_str[] and wait for "y" or "n" followed by Enter. * * Return true if "y". * Return false if "n" or not interactive. */ int get_yes_no(void) { char *cp; char buf[MAX_CMD_LEN]; #if 0 if (!isatty(0)) { return false; } #endif for (;;) { if ((cp = get_string(buf, sizeof(buf))) == NULL) { return false; } str_tolower(cp); switch (*cp) { case 'n': return false; case 'y': return true; } } } /* * Display the result of a command, * or store the pointer to the text of the listed expression * into result_str if compiled for the library. * * Return true if successful. */ int return_result(en) int en; /* equation space number the result is in */ { if (empty_equation_space(en)) { return false; } #if LIBRARY make_fractions_and_group(en); if (factor_int_flag) { factor_int_equation(en); } free_result_str(); #if 1 /* Set this to 1 to allow display2d to decide library output mode. */ if (display2d) { result_str = flist_equation_string(en); if (result_str == NULL) result_str = list_equation(en, false); } else { result_str = list_equation(en, false); } #else /* For feeding command output to the next command's input only. */ result_str = list_equation(en, false); #endif result_en = en; if (gfp == stdout) { return(result_str != NULL); } #endif return(list_sub(en) != 0); } /* * Free any malloc()ed result_str, so there won't be a memory leak * in the symbolic math library. */ void free_result_str(void) { if (result_str) { free(result_str); result_str = NULL; } result_en = -1; } /* * Return true if the first word in the passed string is "all". */ int is_all(cp) char *cp; { return(strcmp_tospace(cp, "all") == 0); } /* * Process an equation number range given in text string "*cpp". * Skip past all spaces and update "*cpp" to point to the next argument if successful. * If no equation number or range is given, or it is invalid, assume the current equation is wanted and * don't skip anything. * * Return true if successful, * with the starting equation number in "*ip" * and ending equation number in "*jp". */ int get_range(cpp, ip, jp) char **cpp; int *ip, *jp; { int i; char *cp; int rv; cp = skip_comma_space(*cpp); if (is_all(cp)) { cp = skip_param(cp); *ip = 0; *jp = n_equations - 1; while (*jp > 0 && n_lhs[*jp] == 0) (*jp)--; } else { if (*cp == '0') { goto use_current; } if (isdigit(*cp)) { *ip = strtol(cp, &cp, 10) - 1; } else { *ip = cur_equation; } if (*cp != '-') { if (*cp == '\0' || *cp == ',' || isspace(*cp)) { if (not_defined(*ip)) { return false; } *jp = *ip; *cpp = skip_comma_space(cp); return true; } else { use_current: *jp = *ip = cur_equation; #if 1 rv = !empty_equation_space(cur_equation); /* don't display error message */ if (rv) { debug_string(1, _("Defaulting to the current equation space.")); } else { debug_string(1, _("Defaulting to current empty equation space.")); } #else rv = !current_not_defined(); /* display an error message if error */ if (rv) { debug_string(1, _("Defaulting to the current equation space.")); } #endif return rv; } } (cp)++; if (*cp == '0') { goto use_current; } if (isdigit(*cp)) { *jp = strtol(cp, &cp, 10) - 1; } else { *jp = cur_equation; } if (*cp && !isspace(*cp)) { goto use_current; } if (*ip < 0 || *ip >= N_EQUATIONS || *jp < 0 || *jp >= N_EQUATIONS) { error(_("Invalid equation number (out of range).")); return false; } if (*jp < *ip) { i = *ip; *ip = *jp; *jp = i; } } cp = skip_comma_space(cp); for (i = *ip; i <= *jp; i++) { if (n_lhs[i] > 0) { *cpp = cp; return true; } } error(_("No expressions defined in specified range.")); return false; } /* * This function is provided to make sure there is nothing else on a command line. * * Returns true if any non-space characters are encountered before the end of the string * and an error message is printed. * Otherwise just returns false indicating everything is OK. */ int extra_characters(cp) char *cp; /* command line string */ { if (cp) { cp = skip_comma_space(cp); if (*cp) { printf(_("\nError: \"%s\" not required on input line.\n"), cp); error(_("Extra characters or unrecognized argument.")); return true; } } return false; } /* * get_range() if it is the last possible option on the command line, * otherwise display an error message and return false. */ int get_range_eol(cpp, ip, jp) char **cpp; int *ip, *jp; { if (!get_range(cpp, ip, jp)) { return false; } if (extra_characters(*cpp)) { return false; } return true; } /* * Skip over space characters. */ char * skip_space(cp) char *cp; /* character pointer */ { if (cp) { while (*cp && isspace(*cp)) cp++; } return cp; } /* * Skip over a possible comma and space characters. */ char * skip_comma_space(cp) char *cp; /* character pointer */ { if (cp) { cp = skip_space(cp); if (*cp == ',') cp = skip_space(cp + 1); } return cp; } /* * Enhanced decimal strtol(). * Skips trailing spaces or commas. */ long decstrtol(cp, cpp) char *cp, **cpp; { long l; l = strtol(cp, cpp, 10); if (cpp && *cpp && cp != *cpp) { *cpp = skip_comma_space(*cpp); } return l; } /* * Return true if passed character is a Mathomatic command parameter delimiter. */ int isdelimiter(ch) int ch; { return(isspace(ch) || ch == ',' || ch == '='); } /* * Skip over the current parameter in a Mathomatic command line string. * Parameters are usually separated with spaces or a comma or equals sign. * * Returns a string (character pointer) to the next parameter. */ char * skip_param(cp) char *cp; { if (cp) { while (*cp && (!isascii(*cp) || !isdelimiter(*cp))) { cp++; } cp = skip_space(cp); if (*cp && isdelimiter(*cp)) { cp = skip_space(cp + 1); } } return(cp); } /* * Compare strings up to the end or the first space or parameter delimiter, * ignoring alphabetic case. * * Returns zero on exact match, otherwise non-zero if strings are different. */ int strcmp_tospace(cp1, cp2) char *cp1, *cp2; { char *cp1a, *cp2a; #if DEBUG if (cp1 == NULL || cp2 == NULL) error_bug("NULL pointer passed to strcmp_tospace()."); #endif for (cp1a = cp1; *cp1a && !isdelimiter(*cp1a); cp1a++) ; for (cp2a = cp2; *cp2a && !isdelimiter(*cp2a); cp2a++) ; return strncasecmp(cp1, cp2, max(cp1a - cp1, cp2a - cp2)); } /* * Return the number of "level" additive type operators. */ int level_plus_count(p1, n1, level) token_type *p1; /* expression pointer */ int n1; /* expression length */ int level; /* parentheses level number to check */ { int i; int count = 0; for (i = 1; i < n1; i += 2) { if (p1[i].level == level) { switch (p1[i].token.operatr) { case PLUS: case MINUS: count++; } } } return count; } /* * Return the number of level 1 additive type operators. */ int level1_plus_count(p1, n1) token_type *p1; /* expression pointer */ int n1; /* expression length */ { return level_plus_count(p1, n1, min_level(p1, n1)); } /* * Return the count of variables in an expression. */ int var_count(p1, n1) token_type *p1; /* expression pointer */ int n1; /* expression length */ { int i; int count = 0; for (i = 0; i < n1; i += 2) { if (p1[i].kind == VARIABLE) { count++; } } return count; } /* * Set "*vp" if single variable expression. * * Return true if expression contains no variables. */ int no_vars(source, n, vp) token_type *source; /* expression pointer */ int n; /* expression length */ long *vp; /* variable pointer */ { int j; int found = false; if (*vp) { return(var_count(source, n) == 0); } for (j = 0; j < n; j += 2) { if (source[j].kind == VARIABLE) { if ((source[j].token.variable & VAR_MASK) <= SIGN) continue; if (*vp) { if (*vp != source[j].token.variable) { *vp = 0; break; } } else { found = true; *vp = source[j].token.variable; } } } return(!found); } /* * Return true if expression contains infinity or NaN (Not a Number). */ int exp_contains_infinity(p1, n1) token_type *p1; /* expression pointer */ int n1; /* expression length */ { int i; for (i = 0; i < n1; i++) { if (p1[i].kind == CONSTANT && !isfinite(p1[i].token.constant)) { return true; } } return false; } /* * Return true if expression contains NaN (Not a Number). */ int exp_contains_nan(p1, n1) token_type *p1; /* expression pointer */ int n1; /* expression length */ { int i; for (i = 0; i < n1; i++) { if (p1[i].kind == CONSTANT && isnan(p1[i].token.constant)) { return true; } } return false; } /* * Return true if expression is numeric (not symbolic). * Pseudo-variables e, pi, i, and sign are considered numeric. */ int exp_is_numeric(p1, n1) token_type *p1; /* expression pointer */ int n1; /* expression length */ { int i; for (i = 0; i < n1; i++) { if (p1[i].kind == VARIABLE && (p1[i].token.variable & VAR_MASK) > SIGN) { return false; /* not numerical (contains a variable) */ } } return true; } /* * Test if expression contains an absolute value. * Return true if it does. */ int exp_is_absolute(p1, n1) token_type *p1; /* expression pointer */ int n1; /* expression length */ { int i; int level; for (i = n1 - 2; i > 2; i -= 2) { if (p1[i].token.operatr != POWER) continue; level = p1[i].level; if (p1[i+1].level == level && p1[i+1].kind == CONSTANT && fmod(p1[i+1].token.constant, 1.0) != 0.0) { level++; if (p1[i-2].token.operatr == POWER && p1[i-2].level == level && p1[i-1].level == level && p1[i-1].kind == CONSTANT) { return true; } } } return false; } /* * Check for division by zero. * * Display a warning and return true if passed double is 0. */ int check_divide_by_zero(denominator) double denominator; { if (denominator == 0) { warning(_("Division by zero.")); return true; } return false; } #if CYGWIN || MINGW /* * dirname(3) function for Microsoft Windows. * dirname(3) strips the non-directory suffix from a filename. */ char * dirname_win(cp) char *cp; /* string containing filename to modify */ { int i; if (cp == NULL) return("."); i = strlen(cp); while (i >= 0 && cp[i] != '\\' && cp[i] != '/') i--; if (i < 0) return("."); cp[i] = '\0'; return(cp); } #endif #if !SECURE /* * Load set options from startup file "~/.mathomaticrc". * * Return false if there was an error reading the startup file, * otherwise return true. */ int load_rc(return_true_if_no_file, ofp) int return_true_if_no_file; FILE *ofp; /* if non-NULL, display each line as read in to this file */ { FILE *fp = NULL; char buf[MAX_CMD_LEN]; char *cp; int rv = true; cp = getenv("HOME"); if (cp) { snprintf(rc_file, sizeof(rc_file), "%s/%s", cp, ".mathomaticrc"); fp = fopen(rc_file, "r"); } #if CYGWIN || MINGW if (fp == NULL && cp) { snprintf(rc_file, sizeof(rc_file), "%s/%s", cp, "mathomatic.rc"); fp = fopen(rc_file, "r"); } if (fp == NULL && dir_path) { snprintf(rc_file, sizeof(rc_file), "%s/%s", dir_path, "mathomatic.rc"); fp = fopen(rc_file, "r"); } #endif if (fp == NULL) { if (return_true_if_no_file) { return true; } else { perror(rc_file); return false; } } if (!quiet_mode && !eoption) { printf(_("Loading startup set options from \"%s\".\n"), rc_file); } while ((cp = fgets(buf, sizeof(buf), fp)) != NULL) { if (ofp) fprintf(ofp, "%s", cp); set_error_level(cp); if (!set_options(cp, true)) rv = false; } if (fclose(fp)) { rv = false; perror(rc_file); } return rv; } #if 0 /* not currently used */ /* * Display set options from startup file "~/.mathomaticrc". * * Return false if there was an error reading the startup file, * otherwise return true. */ int display_rc(ofp) FILE *ofp; { FILE *fp = NULL; char buf[MAX_CMD_LEN]; char *cp; int rv = true; printf(_("Displaying startup set options from \"%s\":\n\n"), rc_file); fp = fopen(rc_file, "r"); if (fp == NULL) { perror(rc_file); return false; } while ((cp = fgets(buf, sizeof(buf), fp)) != NULL) { fprintf(ofp, "%s", cp); } if (fclose(fp)) { rv = false; perror(rc_file); } return rv; } #endif #endif