/* * Mathomatic expression and equation display routines. * Color mode routines, too. * * 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 WIN32_CONSOLE_COLORS /* The WIN32_CONSOLE_COLORS code was contributed by Doug Snead for the MinGW C compiler. */ #include #include #define FOREGROUND_BLACK 0 #define FOREGROUND_YELLOW (FOREGROUND_RED|FOREGROUND_GREEN) #define FOREGROUND_MAGENTA (FOREGROUND_BLUE|FOREGROUND_RED) #define FOREGROUND_CYAN (FOREGROUND_BLUE|FOREGROUND_GREEN) #define FOREGROUND_WHITE (FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE) /* MS-Windows color array for coloring mathematical expressions, warnings, and errors. */ static short windows_carray[] = { FOREGROUND_GREEN, FOREGROUND_YELLOW, /* warning text color */ FOREGROUND_RED, /* error text color */ FOREGROUND_MAGENTA, FOREGROUND_BLUE, FOREGROUND_CYAN, }; extern HANDLE hOut; #endif /* ANSI terminal color code array for 8 color ANSI; we don't use black or white */ /* because the background may be the same color, so there are only 6 colors here. */ static int carray[] = { 32, /* must be green (default color) */ 33, /* must be yellow (for warnings) */ 31, /* must be red (for errors) */ 34, /* must be blue (for prompts) */ 35, /* magenta */ 36, /* cyan */ }; #define EQUATE_STRING " = " /* string displayed between the LHS and RHS of equations */ #define MODULUS_STRING " % " /* string displayed for the modulus operator */ static int flist_sub(token_type *p1, int n, int out_flag, char *string, int sbuffer_size, int pos, int *highp, int *lowp); static int flist_recurse(token_type *p1, int n, int out_flag, char *string, int sbuffer_size, int line, int pos, int cur_level, int *highp, int *lowp); /* Bright HTML color array. */ /* Used when HTML output and "set color" and "set bold" options are enabled. */ /* Good looking with a dark background. */ static char *bright_html_carray[] = { "#00FF00", /* must be bright green (default color) */ "#FFFF00", /* must be bright yellow (for warnings) */ "#FF0000", /* must be bright red (for errors) */ "#0000FF", /* must be bright blue (for prompts) */ "#FF9000", "#FF00FF", "#00FFFF", }; /* Dim HTML color array for color HTML output. */ /* Used for HTML output with "set color" and "set no bold" options. */ /* Good looking with a white background. */ static char *html_carray[] = { "green", "olive", "red", "navy", "maroon", "purple", "teal", }; /* * Reset terminal attributes function. * Turn color off if color mode is on. * Subsequent output will have no color until the next call to set_color(). */ void reset_attr(void) { FILE *fp; if (html_flag == 2) { fp = gfp; } else { fp = stdout; } #if !LIBRARY fflush(NULL); /* flush all output */ #endif if (color_flag && cur_color >= 0) { if (html_flag) { fprintf(fp, ""); } else { #if WIN32_CONSOLE_COLORS if (color_flag == 2) { fprintf(fp, "\033[0m"); } else { SetConsoleTextAttribute(hOut, FOREGROUND_WHITE); } #else fprintf(fp, "\033[0m"); #endif } fflush(fp); } cur_color = -1; } /* * Set the current color on the display. * Subsequent output will have the color in carray[color % ARR_CNT(carray)] for ANSI color mode. * Other color modes work similarly. * Range for color is 0 to INT_MAX. * * Return actual color number displayed or -1 if no color. */ int set_color(color) int color; { int rv = -1; if (html_flag != 2 && gfp != stdout) { return rv; } if (color_flag) { if (cur_color == color) /* color already set */ return rv; if (html_flag) { if (cur_color >= 0) { fprintf(gfp, ""); } if (bold_colors) { fprintf(gfp, "", bright_html_carray[rv = (color % ARR_CNT(bright_html_carray))]); } else { fprintf(gfp, "", html_carray[rv = (color % ARR_CNT(html_carray))]); } } else { #if WIN32_CONSOLE_COLORS if (color_flag == 2) { fprintf(gfp, "\033[%d;%dm", bold_colors, carray[rv = (color % ARR_CNT(carray))]); } else { short attr = windows_carray[rv = (color % ARR_CNT(windows_carray))]; if (bold_colors) attr |= FOREGROUND_INTENSITY; SetConsoleTextAttribute(hOut, attr); } #else fprintf(gfp, "\033[%d;%dm", bold_colors, carray[rv = (color % ARR_CNT(carray))]); #endif } cur_color = color; } return rv; } /* * Set normal text color for subsequent output. */ void default_color(set_no_color_flag) int set_no_color_flag; /* If true, set no color even if text_color is set. Normally this would be false. */ { if (html_flag != 2 && gfp != stdout) { return; } if (color_flag && cur_color >= 0) { if (html_flag) { fprintf(gfp, ""); } else { #if WIN32_CONSOLE_COLORS if (color_flag == 2) { fprintf(gfp, "\033[0m"); } else { SetConsoleTextAttribute(hOut, FOREGROUND_WHITE); } #else fprintf(gfp, "\033[0m"); #endif } } cur_color = -1; if (text_color >= 0 && !set_no_color_flag) { set_color(text_color); } fflush(gfp); } /* * Display all possible colors for this color mode. * * Return true if successful. */ int display_all_colors(void) { int i, j; i = 0; default_color(true); if (set_color(i) < 0) { default_color(false); return false; } do { printf("#"); i++; j = set_color(i); } while (j > 0); default_color(false); return(j >= 0); } /* * Trim the trailing zeros from a string, after the decimal point. */ static void trim_zeros(buf) char *buf; { int j; j = strlen(buf) - 1; for (; j >= 0; j--) { if (buf[j] == '0') continue; if (buf[j] == '.') { if (buf[j+1]) buf[j+2] = '\0'; } else { break; } } } /* * Display the expression or equation stored in equation space "n" in single-line format. * * Return length (number of screen columns) of output line. */ int list1_sub(n, export_flag) int n; /* equation space number */ int export_flag; /* non-zero for exportable format (readable by other math programs) */ /* 1 for Maxima, 2 for other, 3 for gnuplot, 4 for hexadecimal */ { int len = 0; if (empty_equation_space(n)) return 0; if ((export_flag == 0 || export_flag == 4) && !high_prec) { len += fprintf(gfp, "#%d: ", n + 1); } len += list_proc(lhs[n], n_lhs[n], export_flag); if (n_rhs[n]) { len += fprintf(gfp, EQUATE_STRING); len += list_proc(rhs[n], n_rhs[n], export_flag); } if (export_flag == 1) { len += fprintf(gfp, ";"); } #if CYGWIN fprintf(gfp, "\r\n"); /* might be redirecting to a Microsoft text file */ #else fprintf(gfp, "\n"); #endif return len; } /* * Display the expression or equation stored in equation space "n". * * Return the total width of the output (number of screen columns) * or zero on failure. */ int list_sub(n) int n; /* equation space number */ { if (empty_equation_space(n)) return 0; make_fractions_and_group(n); if (factor_int_flag) { factor_int_equation(n); } if (display2d) { /* display in fraction format */ return flist_equation(n); } else { /* display in single-line format */ return list1_sub(n, false); } } #if !SILENT void list_debug(level, p1, n1, p2, n2) int level; token_type *p1; int n1; token_type *p2; int n2; { if (debug_level >= level) { if (level >= -2) { fprintf(gfp, _("level %d: "), level); } list_proc(p1, n1, false); if (p2 && n2 > 0) { fprintf(gfp, EQUATE_STRING); list_proc(p2, n2, false); } fprintf(gfp, "\n"); } } #endif /* * Return the allocated string name of the given Mathomatic variable, * or NULL if none. * * Does not return the actual variable name, use list_var() for that. */ char * var_name(v) long v; /* Mathomatic variable */ { char *cp = NULL; long l; l = (labs(v) & VAR_MASK) - VAR_OFFSET; if (l >= 0 && l < MAX_VAR_NAMES) { cp = var_names[l]; } return cp; } /* * Convert the passed Mathomatic variable to an ASCII variable name. * The ASCII variable name is stored in the global var_str[]. * * Return the length of the variable name string in var_str[]. * Nothing is displayed. * * If (lang_code == 0), use standard Mathomatic format. * If (lang_code > 0), make variable compatible with the language defined in the enumeration language_list defined in am.h * If (lang_code < 0), create an exportable variable name: -1 for Maxima, -2 for other, -3 for gnuplot, -4 for hexadecimal, * -5 for mathomatic-only variable format. */ int list_var(v, lang_code) long v; /* variable to convert */ int lang_code; /* language code */ { int j; int from_memory = false; char *cp = NULL; var_str[0] = '\0'; switch (labs(v) & VAR_MASK) { case V_NULL: return(strlen(var_str)); case SIGN: cp = "sign"; break; case IMAGINARY: switch (lang_code) { case -3: cp = "{0,1}"; break; case 0: case -4: case -2: cp = "i"; break; case -5: cp = "i#"; break; case -1: cp = "%i"; break; case PYTHON: cp = "1j"; break; default: cp = "1.0i"; break; } break; case V_E: switch (lang_code) { case -3: cp = "exp(1.0)"; break; case -1: cp = "%e"; break; case C: cp ="M_E"; break; case JAVA: cp = "Math.E"; break; case PYTHON: cp = "math.e"; break; case -5: cp = "e#"; break; default: cp = "e"; break; } break; case V_PI: switch (lang_code) { case -1: cp = "%pi"; break; case -5: cp ="pi#"; break; case C: cp = "M_PI"; break; case JAVA: cp = "Math.PI"; break; case PYTHON: cp = "math.pi"; break; default: cp = "pi"; break; } break; case MATCH_ANY: cp = "all"; break; default: cp = var_name(v); from_memory = true; break; } if (cp) { j = (labs(v) >> VAR_SHIFT) & SUBSCRIPT_MASK; if (j) { /* for "sign" variables */ snprintf(var_str, sizeof(var_str), "%s%d", cp, j - 1); } else { my_strlcpy(var_str, cp, sizeof(var_str)); } } else { my_strlcpy(var_str, "bad_variable", sizeof(var_str)); } /* Make valid C type variable if exporting or generating code: */ if (from_memory) { switch (lang_code) { case 0: case -4: case -5: break; default: for (j = 0; var_str[j] && var_str[j] != '('; j++) { if (strchr("_[]", var_str[j]) == NULL && !isalnum(var_str[j])) { var_str[j] = '_'; } } break; } } return(strlen(var_str)); } /* * Display an expression in single-line format. * Use color if color mode is set. * * Return number of characters output (excluding escape sequences). */ int list_proc(p1, n, export_flag) token_type *p1; /* expression pointer */ int n; /* length of expression */ int export_flag; /* flag for exportable format (usually false) */ /* 1 for Maxima, 2 for other, 3 for gnuplot, 4 for hexadecimal */ { return list_string_sub(p1, n, true, NULL, export_flag); } /* * Store the expression from the specified equation space in a text string in single-line format. * String should be freed with free() when done. * * Returns text string, or NULL if error. */ char * list_equation(n, export_flag) int n; /* equation space number */ int export_flag; /* flag for exportable format (usually false) */ { int len; char *cp; if (empty_equation_space(n)) return NULL; len = list_string(lhs[n], n_lhs[n], NULL, export_flag); if (n_rhs[n]) { len += strlen(EQUATE_STRING); len += list_string(rhs[n], n_rhs[n], NULL, export_flag); } len += 2; /* for possible semicolon and terminating null character */ cp = (char *) malloc(len); if (cp == NULL) { error(_("Out of memory (can't malloc(3)).")); return NULL; } list_string(lhs[n], n_lhs[n], cp, export_flag); if (n_rhs[n]) { strcat(cp, EQUATE_STRING); list_string(rhs[n], n_rhs[n], &cp[strlen(cp)], export_flag); } if (export_flag == 1) { strcat(cp, ";"); } return cp; } /* * Store an expression in a text string. * String should be freed with free() when done. * * Return string, or NULL if error. */ char * list_expression(p1, n, export_flag) token_type *p1; /* expression pointer */ int n; /* length of expression */ int export_flag; { int len; char *cp; if (n <= 0) { return NULL; } len = list_string(p1, n, NULL, export_flag); len++; /* for terminating null character */ cp = (char *) malloc(len); if (cp == NULL) { error(_("Out of memory (can't malloc(3)).")); return NULL; } list_string(p1, n, cp, export_flag); return cp; } /* * Convert an expression to a text string and store in "string" if * "string" is not NULL. "string" need not be initialized, * but must be long enough to contain the expression. * To find the storage size needed, call with "string" set to NULL first. * * Return length (number of characters). */ int list_string(p1, n, string, export_flag) token_type *p1; /* expression pointer */ int n; /* length of expression */ char *string; /* buffer to save output to or NULL pointer */ int export_flag; { return list_string_sub(p1, n, false, string, export_flag); } #define APPEND(str) { if (string) { strcpy(&string[len], str); } if (outflag) { fprintf(gfp, "%s", str); } len += strlen(str); } #define APPEND2(str) { if (string) { if ((sbuffer_size - current_len) > 0) my_strlcpy(&string[current_len], str, sbuffer_size - current_len); } else { fprintf(gfp, "%s", str); } current_len += strlen(str); } int list_string_sub(p1, n, outflag, string, export_flag) token_type *p1; /* expression pointer */ int n; /* length of expression */ int outflag; /* if true, output to gfp */ char *string; /* buffer to save output to or NULL pointer */ int export_flag; /* flag for exportable format (usually false) */ /* 1 for Maxima, 2 for other, 3 for gnuplot, 4 for hexadecimal */ { int i, j, k, i1; int min1; int cur_level; char *cp; int len = 0; char buf[500], buf2[500]; int export_precision; int cflag, power_flag; cflag = (outflag && (export_flag == 0 || export_flag == 4)); if (cflag) set_color(0); if (string) string[0] = '\0'; if (high_prec) export_precision = 20; else export_precision = DBL_DIG; cur_level = min1 = min_level(p1, n); for (i = 0; i < n; i++) { power_flag = false; if (export_flag == 0 && !high_prec) { for (j = i - 1; j <= (i + 1); j++) { if ((j - 1) >= 0 && (j + 1) < n && p1[j].kind == OPERATOR && (p1[j].token.operatr == POWER || p1[j].token.operatr == FACTORIAL) && p1[j-1].level == p1[j].level && p1[j+1].level == p1[j].level && ((j + 2) >= n || p1[j+2].level != (p1[j].level - 1) || (p1[j+2].token.operatr < POWER)) && ((j - 2) < 0 || p1[j-2].level != (p1[j].level - 1) || (p1[j-2].token.operatr < POWER))) { power_flag = true; break; } } } j = cur_level - p1[i].level; if (power_flag) k = abs(j) - 1; else k = abs(j); for (i1 = 1; i1 <= k; i1++) { if (j > 0) { cur_level--; APPEND(")"); if (cflag) set_color(cur_level-min1); } else { cur_level++; if (cflag) set_color(cur_level-min1); APPEND("("); } } switch (p1[i].kind) { case CONSTANT: if (p1[i].token.constant == 0.0) { p1[i].token.constant = 0.0; /* fix -0 */ } if (export_flag == 4) { snprintf(buf, sizeof(buf), "%a", p1[i].token.constant); } else if (export_flag == 3) { snprintf(buf, sizeof(buf), "%#.*g", DBL_DIG, p1[i].token.constant); trim_zeros(buf); } else if (export_flag || high_prec) { snprintf(buf, sizeof(buf), "%.*g", export_precision, p1[i].token.constant); } else if (finance_option >= 0) { #if THOUSANDS_SEPARATOR /* Fails miserably in MinGW and possibly others, displaying nothing but the format string. */ snprintf(buf, sizeof(buf), "%'.*f", finance_option, p1[i].token.constant); #else snprintf(buf, sizeof(buf), "%.*f", finance_option, p1[i].token.constant); #endif } else { if (p1[i].token.constant < 0.0 && (i + 1) < n && p1[i+1].level == p1[i].level && (p1[i+1].token.operatr >= POWER)) { snprintf(buf, sizeof(buf), "(%.*g)", precision, p1[i].token.constant); } else { snprintf(buf, sizeof(buf), "%.*g", precision, p1[i].token.constant); } APPEND(buf); break; } if (p1[i].token.constant < 0.0) { snprintf(buf2, sizeof(buf2), "(%s)", buf); APPEND(buf2); } else { APPEND(buf); } break; case VARIABLE: list_var(p1[i].token.variable, 0 - export_flag); APPEND(var_str); break; case OPERATOR: cp = _("(unknown operator)"); switch (p1[i].token.operatr) { case PLUS: cp = " + "; break; case MINUS: cp = " - "; break; case TIMES: cp = "*"; break; case DIVIDE: cp = "/"; break; case IDIVIDE: cp = "//"; break; case MODULUS: cp = MODULUS_STRING; break; case POWER: if (power_starstar || export_flag == 3) { cp = "**"; } else { cp = "^"; } break; case FACTORIAL: cp = "!"; i++; break; } APPEND(cp); break; } } for (j = cur_level - min1; j > 0;) { APPEND(")"); j--; if (cflag) set_color(j); } if (cflag) default_color(false); return len; } /* * Return 1 (true) or -1 if expression is a valid integer expression for * list_code(). * Return 0 if it is definitely a non-integer expression. * Return -1 if it contains non-integer divide operators, but is OK otherwise. */ int int_expr(p1, n) token_type *p1; /* expression pointer */ int n; /* length of expression */ { int i; int rv = 1; for (i = 0; i < n; i++) { switch (p1[i].kind) { case CONSTANT: if (fmod(p1[i].token.constant, 1.0) != 0.0) { return 0; } break; case VARIABLE: if (p1[i].token.variable < IMAGINARY) { return 0; } break; case OPERATOR: if (p1[i].token.operatr == DIVIDE) rv = -1; break; } } return rv; } /* * Display an equation space as C, Java, or Python code. * * Return length of output (number of characters). */ int list_code_equation(en, language, int_flag) int en; /* equation space number */ enum language_list language; int int_flag; /* integer arithmetic flag */ { int len = 0; if (empty_equation_space(en)) return 0; len += list_code(lhs[en], &n_lhs[en], true, NULL, language, int_flag); if (n_rhs[en]) { len += fprintf(gfp, EQUATE_STRING); len += list_code(rhs[en], &n_rhs[en], true, NULL, language, int_flag); } switch (language) { case C: case JAVA: len += fprintf(gfp, ";"); break; default: break; } fprintf(gfp, "\n"); return len; } /* * Convert the specified equation space to a string of C, Java, or Python code. * String should be freed with free() when done. * * Return string, or NULL if error. */ char * string_code_equation(en, language, int_flag) int en; /* equation space number */ enum language_list language; int int_flag; /* integer arithmetic flag */ { int len; char *cp; if (empty_equation_space(en)) return NULL; len = list_code(lhs[en], &n_lhs[en], false, NULL, language, int_flag); if (n_rhs[en]) { len += strlen(EQUATE_STRING); len += list_code(rhs[en], &n_rhs[en], false, NULL, language, int_flag); } len += 2; /* for possible semicolon and terminating null character */ cp = (char *) malloc(len); if (cp == NULL) { error(_("Out of memory (can't malloc(3)).")); return NULL; } list_code(lhs[en], &n_lhs[en], false, cp, language, int_flag); if (n_rhs[en]) { strcat(cp, EQUATE_STRING); list_code(rhs[en], &n_rhs[en], false, &cp[strlen(cp)], language, int_flag); } switch (language) { case C: case JAVA: strcat(cp, ";"); break; default: break; } return cp; } /* * Output C, Java, or Python code for an expression. * Expression might be modified by this function, though it remains equivalent. * * Return length of output (number of characters). */ int list_code(equation, np, outflag, string, language, int_flag) token_type *equation; /* equation side pointer */ int *np; /* pointer to length of equation side */ int outflag; /* if true, output to gfp */ char *string; /* buffer to save output to or NULL pointer */ enum language_list language; /* see enumeration language_list in am.h */ int int_flag; /* integer arithmetic flag, should work with any language */ { int i, j, k, i1, i2; int min1; int cur_level; char *cp; char buf[500], buf2[500]; int len = 0; if (string) string[0] = '\0'; min1 = min_level(equation, *np); if (*np > 1) min1--; cur_level = min1; for (i = 0; i < *np; i++) { j = cur_level - equation[i].level; k = abs(j); for (i1 = 1; i1 <= k; i1++) { if (j > 0) { cur_level--; APPEND(")"); } else { cur_level++; for (i2 = i + 1; i2 < *np && equation[i2].level >= cur_level; i2 += 2) { if (equation[i2].level == cur_level) { switch (equation[i2].token.operatr) { case POWER: if (equation[i2-1].level == cur_level && equation[i2+1].level == cur_level && equation[i2+1].kind == CONSTANT && equation[i2+1].token.constant == 2.0) { equation[i2].token.operatr = TIMES; equation[i2+1] = equation[i2-1]; } else { if (!int_flag) { switch (language) { case C: APPEND("pow"); break; case JAVA: APPEND("Math.pow"); break; default: break; } } } break; case FACTORIAL: APPEND("factorial"); break; } break; } } APPEND("("); } } switch (equation[i].kind) { case CONSTANT: if (equation[i].token.constant == 0.0) { equation[i].token.constant = 0.0; /* fix -0 */ } if (int_flag) { snprintf(buf, sizeof(buf), "%.0f", equation[i].token.constant); } else { snprintf(buf, sizeof(buf), "%#.*g", DBL_DIG, equation[i].token.constant); trim_zeros(buf); } /* Here we will need to parenthesize negative numbers to make -2**x work the same with Python: */ if (equation[i].token.constant < 0) { snprintf(buf2, sizeof(buf2), "(%s)", buf); APPEND(buf2); } else { APPEND(buf); } break; case VARIABLE: if (int_flag && (language == C || language == JAVA) && equation[i].token.variable == IMAGINARY) { APPEND("1i"); } else { list_var(equation[i].token.variable, language); APPEND(var_str); } break; case OPERATOR: cp = _("(unknown operator)"); switch (equation[i].token.operatr) { case PLUS: cp = " + "; break; case MINUS: cp = " - "; break; case TIMES: cp = "*"; break; case IDIVIDE: if (language == PYTHON) { cp = "//"; break; } case DIVIDE: cp = "/"; break; case MODULUS: cp = MODULUS_STRING; break; case POWER: if (int_flag || language == PYTHON) { cp = "**"; } else { cp = ", "; } break; case FACTORIAL: cp = ""; i++; break; } APPEND(cp); break; } } for (j = cur_level - min1; j > 0; j--) { APPEND(")"); } return len; } /* global variables for the flist functions below */ static int cur_line; /* current line */ static int cur_pos; /* current position in the current line on the screen */ /* * Return a multi-line C string containing the specified equation space in 2D multi-line fraction format. * The string should be freed after use with free(3). * * Color mode is not used. * * The equation sides must first be basically simplified and prepared by fractions_and_group(), * for proper formatting. * * Return NULL on failure. * Result will be limited to current_columns columns, TEXT_ROWS lines. * Exceeding current_columns will result in truncation, * exceeding TEXT_ROWS will result in failure and NULL return. * current_columns is set in malloc_vscreen(). */ char * flist_equation_string(n) int n; /* equation space number */ { int i; int len, cur_len, buf_len; int pos; int high = 0, low = 0; int max_line = 0, min_line = 0; int screen_line; char *cp; if (empty_equation_space(n)) return NULL; if (!malloc_vscreen()) return NULL; for (i = 0; i < TEXT_ROWS; i++) { vscreen[i][0] = '\0'; } cur_line = 0; cur_pos = 0; len = flist_sub(lhs[n], n_lhs[n], false, NULL, current_columns, 0, &max_line, &min_line); if (n_rhs[n]) { len += strlen(EQUATE_STRING); len += flist_sub(rhs[n], n_rhs[n], false, NULL, current_columns, 0, &high, &low); if (high > max_line) max_line = high; if (low < min_line) min_line = low; } if ((max_line - min_line) >= TEXT_ROWS) return NULL; for (cur_line = max_line, screen_line = 0; cur_line >= min_line; cur_line--, screen_line++) { pos = cur_pos = 0; pos += flist_sub(lhs[n], n_lhs[n], true, vscreen[screen_line], current_columns, pos, &high, &low); if (n_rhs[n]) { if (cur_line == 0) { cur_pos += strlen(EQUATE_STRING); cur_len = strlen(vscreen[screen_line]); if ((current_columns - cur_len) > 0) my_strlcpy(&vscreen[screen_line][cur_len], EQUATE_STRING, current_columns - cur_len); } pos += strlen(EQUATE_STRING); pos += flist_sub(rhs[n], n_rhs[n], true, vscreen[screen_line], current_columns, pos, &high, &low); } } if (screen_line <= 0) return NULL; for (i = 0, buf_len = 1; i < screen_line; i++) { buf_len += strlen(vscreen[i]); buf_len++; /* For newlines */ } cp = (char *) malloc(buf_len); if (cp == NULL) { error(_("Out of memory (can't malloc(3)).")); return NULL; } cp[0] = '\0'; for (i = 0; i < screen_line; i++) { strcat(cp, vscreen[i]); strcat(cp, "\n"); } return(cp); } /* * Display an equation space in 2D multi-line fraction format. * Use color if color mode is set. * * The equation sides must first be basically simplified and prepared by fractions_and_group(), * for proper display. * * Return the total width of the output (that is, the required number of screen columns) * or zero on failure. */ int flist_equation(n) int n; /* equation space number */ { int sind; char buf[50]; int len, len2, len3, width; int pos; int high = 0, low = 0; int max_line = 0, min_line = 0; int max2_line = 0, min2_line = 0; int use_screen_columns; /* use infinite width if false */ if (empty_equation_space(n)) return 0; #if 1 /* always respect "set columns" */ use_screen_columns = true; #else use_screen_columns = (gfp == stdout); #endif len = snprintf(buf, sizeof(buf), "#%d: ", n + 1); cur_line = 0; cur_pos = 0; sind = n_rhs[n]; len += flist_sub(lhs[n], n_lhs[n], false, NULL, screen_columns, 0, &max_line, &min_line); if (n_rhs[n]) { len += strlen(EQUATE_STRING); make_smaller: len2 = flist_sub(rhs[n], sind, false, NULL, screen_columns, 0, &high, &low); if (screen_columns && use_screen_columns && (len + len2) >= screen_columns && sind > 0) { for (sind--; sind > 0; sind--) { if (rhs[n][sind].level == 1 && rhs[n][sind].kind == OPERATOR) { switch (rhs[n][sind].token.operatr) { case PLUS: case MINUS: case MODULUS: goto make_smaller; } } } goto make_smaller; } if (high > max_line) max_line = high; if (low < min_line) min_line = low; len3 = flist_sub(&rhs[n][sind], n_rhs[n] - sind, false, NULL, screen_columns, 0, &max2_line, &min2_line); } else { len2 = 0; len3 = 0; } width = max(len + len2, len3); if (screen_columns && use_screen_columns && width >= screen_columns) { /* output too wide to fit screen, output in single-line format */ width = list1_sub(n, false); #if CYGWIN fprintf(gfp, "\r\n"); /* Be consistent with list1_sub() output. */ #else fprintf(gfp, "\n"); #endif return width; } fprintf(gfp, "\n"); for (cur_line = max_line; cur_line >= min_line; cur_line--) { pos = cur_pos = 0; if (cur_line == 0) { cur_pos += fprintf(gfp, "%s", buf); } pos += strlen(buf); pos += flist_sub(lhs[n], n_lhs[n], true, NULL, screen_columns, pos, &high, &low); if (n_rhs[n]) { if (cur_line == 0) { cur_pos += fprintf(gfp, "%s", EQUATE_STRING); } pos += strlen(EQUATE_STRING); pos += flist_sub(rhs[n], sind, true, NULL, screen_columns, pos, &high, &low); } fprintf(gfp, "\n"); } if (sind < n_rhs[n]) { /* output second half of split equation that was too wide to display on the screen without splitting */ fprintf(gfp, "\n"); for (cur_line = max2_line; cur_line >= min2_line; cur_line--) { cur_pos = 0; flist_sub(&rhs[n][sind], n_rhs[n] - sind, true, NULL, screen_columns, 0, &high, &low); fprintf(gfp, "\n"); } } fprintf(gfp, "\n"); return width; } /* * Display a line of an expression in 2D fraction format if "out_flag" is true. * The number of the line to output is stored in the global variable cur_line. * 0 is the middle line, lines above are positive, lines below are negative. * Use color if available. * * The following functions are for internal use only, and not to be called, * except by flist_equation() and flist_equation_string(). * * Return the width of the expression (that is, the required number of screen columns). */ static int flist_sub(p1, n, out_flag, string, sbuffer_size, pos, highp, lowp) token_type *p1; /* expression pointer */ int n; /* length of expression */ int out_flag; /* if true, output to gfp or string */ char *string; /* if not NULL, put output to here instead of gfp */ int sbuffer_size; /* string buffer size */ int pos; int *highp, *lowp; { int rv; rv = flist_recurse(p1, n, out_flag, string, sbuffer_size, 0, pos, 1, highp, lowp); if (out_flag && (string == NULL)) { default_color(false); } return rv; } static int flist_recurse(p1, n, out_flag, string, sbuffer_size, line, pos, cur_level, highp, lowp) token_type *p1; int n; int out_flag; /* if true, output to gfp or string */ char *string; /* if not NULL, put output to here instead of gfp */ int sbuffer_size; /* string buffer size */ int line; int pos; int cur_level; int *highp, *lowp; { int i, j, k, i1; int l1, l2; int ii; int stop_at; int div_loc; int len_div; int level; int start_level; int oflag, cflag, html_out, power_flag; int len = 0, len1, len2; int current_len = 0; int high, low; char buf[500]; char *cp; if (string) current_len = strlen(string); start_level = cur_level; *highp = line; *lowp = line; if (n <= 0) { return 0; } oflag = (out_flag && line == cur_line); cflag = (oflag && string == NULL); html_out = ((html_flag == 2) || (html_flag && gfp == stdout)); if (oflag) { for (; cur_pos < pos; cur_pos++) { APPEND2(" "); } } ii = 0; check_again: stop_at = n; div_loc = -1; for (i = ii; i < n; i++) { if (p1[i].kind == OPERATOR && p1[i].token.operatr == DIVIDE) { level = p1[i].level; for (j = i - 2; j > 0; j -= 2) { if (p1[j].level < level) break; } j++; if (div_loc < 0) { div_loc = i; stop_at = j; } else { if (j < stop_at) { div_loc = i; stop_at = j; } else if (j == stop_at) { if (level < p1[div_loc].level) div_loc = i; } } } } for (i = ii; i < n; i++) { power_flag = false; if (i == stop_at) { #if DEBUG if (div_loc < 0) { error_bug("Bug in flist_recurse()."); } #endif j = cur_level - p1[div_loc].level; k = abs(j) - 1; } else { for (j = i - 1; j <= (i + 1); j++) { if ((j - 1) >= ii && (j + 1) < n && p1[j].kind == OPERATOR && (p1[j].token.operatr == POWER || p1[j].token.operatr == FACTORIAL) && p1[j-1].level == p1[j].level && p1[j+1].level == p1[j].level && ((j + 2) >= n || p1[j+2].level != (p1[j].level - 1) || (p1[j+2].token.operatr < POWER)) && ((j - 2) < ii || p1[j-2].level != (p1[j].level - 1) || (p1[j-2].token.operatr < POWER))) { power_flag = true; break; } } j = cur_level - p1[i].level; if (power_flag) k = abs(j) - 1; else k = abs(j); } if (k < 1) { if (cflag) set_color(cur_level-1); } for (i1 = 1; i1 <= k; i1++) { if (j > 0) { cur_level--; len++; if (oflag) { APPEND2(")"); if (cflag) set_color(cur_level-1); } } else { cur_level++; len++; if (oflag) { if (cflag) set_color(cur_level-1); APPEND2("("); } } } if (i == stop_at) { level = p1[div_loc].level; len1 = flist_recurse(&p1[stop_at], div_loc - stop_at, false, string, sbuffer_size, line + 1, pos + len, level, &high, &low); l1 = (2 * (line + 1)) - low; for (j = div_loc + 2; j < n; j += 2) { if (p1[j].level <= level) break; } len2 = flist_recurse(&p1[div_loc+1], j - (div_loc + 1), false, string, sbuffer_size, line - 1, pos + len, level, &high, &low); l2 = (2 * (line - 1)) - high; ii = j; len_div = max(len1, len2); j = 0; if (len1 < len_div) { j = (len_div - len1) / 2; } flist_recurse(&p1[stop_at], div_loc - stop_at, out_flag, string, sbuffer_size, l1, pos + len + j, level, &high, &low); if (high > *highp) *highp = high; if (low < *lowp) *lowp = low; if (oflag) { /* display fraction bar */ if (cflag) set_color(level-1); for (j = 0; j < len_div; j++) { if (html_out) { APPEND2("–"); } else { APPEND2("-"); } } if (cflag) set_color(cur_level-1); } j = 0; if (len2 < len_div) { j = (len_div - len2) / 2; } flist_recurse(&p1[div_loc+1], ii - (div_loc + 1), out_flag, string, sbuffer_size, l2, pos + len + j, level, &high, &low); if (high > *highp) *highp = high; if (low < *lowp) *lowp = low; len += len_div; goto check_again; } switch (p1[i].kind) { case CONSTANT: if (p1[i].token.constant == 0.0) { p1[i].token.constant = 0.0; /* fix -0 */ } if (html_out && isinf(p1[i].token.constant)) { if (p1[i].token.constant < 0) { snprintf(buf, sizeof(buf), "(-∞)"); len += 4; } else { snprintf(buf, sizeof(buf), "∞"); len += 1; } } else if (p1[i].token.constant == -1.0 && (i == 0 || p1[i-1].level < p1[i].level) && (i + 1) < n && p1[i].level == p1[i+1].level && p1[i+1].token.operatr == TIMES) { i++; len += snprintf(buf, sizeof(buf), "-"); } else if (finance_option >= 0) { if (p1[i].token.constant < 0.0) { #if THOUSANDS_SEPARATOR len += snprintf(buf, sizeof(buf), "(%'.*f)", finance_option, p1[i].token.constant); #else len += snprintf(buf, sizeof(buf), "(%.*f)", finance_option, p1[i].token.constant); #endif } else { #if THOUSANDS_SEPARATOR len += snprintf(buf, sizeof(buf), "%'.*f", finance_option, p1[i].token.constant); #else len += snprintf(buf, sizeof(buf), "%.*f", finance_option, p1[i].token.constant); #endif } } else { if (p1[i].token.constant < 0.0 && (i + 1) < n && p1[i+1].level == p1[i].level && (p1[i+1].token.operatr >= POWER)) { len += snprintf(buf, sizeof(buf), "(%.*g)", precision, p1[i].token.constant); } else { len += snprintf(buf, sizeof(buf), "%.*g", precision, p1[i].token.constant); } } if (oflag) APPEND2(buf); break; case VARIABLE: if (html_out && p1[i].token.variable == V_PI) { len += 1; if (oflag) APPEND2("π"); } else if (html_out && p1[i].token.variable == V_E) { len += 1; if (oflag) APPEND2("ê"); } else if (html_out && p1[i].token.variable == IMAGINARY) { len += 1; if (oflag) APPEND2("î"); } else { len += list_var(p1[i].token.variable, 0); if (oflag) APPEND2(var_str); } break; case OPERATOR: /* "len" must be incremented here by the number of columns the display of the operator takes */ switch (p1[i].token.operatr) { case PLUS: cp = " + "; len += 3; break; case MINUS: if (html_out) { cp = " − "; } else { cp = " - "; } len += 3; break; case TIMES: if (html_out) { cp = "·"; } else { cp = "*"; } len++; break; case DIVIDE: cp = "/"; len++; break; case IDIVIDE: cp = "//"; len += 2; break; case MODULUS: cp = MODULUS_STRING; len += strlen(cp); break; case POWER: #if 0 if (html_out && p1[i+1].level == p1[i].level && p1[i+1].kind == CONSTANT) { len += (snprintf(buf, sizeof(buf), "%.*g", precision, p1[i+1].token.constant) - 11); cp = buf; i++; break; } #endif if (power_starstar) { cp = "**"; len += 2; } else { cp = "^"; len++; } break; case FACTORIAL: cp = "!"; len++; i++; break; default: cp = _("(unknown operator)"); len += strlen(cp); break; } if (oflag) APPEND2(cp); break; } } for (j = cur_level - start_level; j > 0;) { cur_level--; len++; j--; if (oflag) { APPEND2(")"); if (j > 0 && cflag) set_color(cur_level-1); } } if (oflag) cur_pos += len; return len; }