diff options
Diffstat (limited to 'src/roff/troff/number.cpp')
-rw-r--r-- | src/roff/troff/number.cpp | 712 |
1 files changed, 712 insertions, 0 deletions
diff --git a/src/roff/troff/number.cpp b/src/roff/troff/number.cpp new file mode 100644 index 0000000..348b557 --- /dev/null +++ b/src/roff/troff/number.cpp @@ -0,0 +1,712 @@ +/* Copyright (C) 1989-2020 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +groff is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or +(at your option) any later version. + +groff 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 General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. */ + + +#include "troff.h" +#include "hvunits.h" +#include "stringclass.h" +#include "mtsm.h" +#include "env.h" +#include "token.h" +#include "div.h" + +const vunits V0; // zero in vertical units +const hunits H0; // zero in horizontal units + +int hresolution = 1; +int vresolution = 1; +int units_per_inch; +int sizescale; + +static bool is_valid_expression(units *v, int scaling_unit, + bool is_parenthesized, + bool is_mandatory = false); +static bool is_valid_expression_start(); + +int get_vunits(vunits *res, unsigned char si) +{ + if (!is_valid_expression_start()) + return 0; + units x; + if (is_valid_expression(&x, si, false /* is_parenthesized */)) { + *res = vunits(x); + return 1; + } + else + return 0; +} + +int get_hunits(hunits *res, unsigned char si) +{ + if (!is_valid_expression_start()) + return 0; + units x; + if (is_valid_expression(&x, si, false /* is_parenthesized */)) { + *res = hunits(x); + return 1; + } + else + return 0; +} + +// for \B + +int get_number_rigidly(units *res, unsigned char si) +{ + if (!is_valid_expression_start()) + return 0; + units x; + if (is_valid_expression(&x, si, false /* is_parenthesized */, + true /* is_mandatory */)) { + *res = x; + return 1; + } + else + return 0; +} + +int get_number(units *res, unsigned char si) +{ + if (!is_valid_expression_start()) + return 0; + units x; + if (is_valid_expression(&x, si, false /* is_parenthesized */)) { + *res = x; + return 1; + } + else + return 0; +} + +int get_integer(int *res) +{ + if (!is_valid_expression_start()) + return 0; + units x; + if (is_valid_expression(&x, 0, false /* is_parenthesized */)) { + *res = x; + return 1; + } + else + return 0; +} + +enum incr_number_result { BAD, ABSOLUTE, INCREMENT, DECREMENT }; + +static incr_number_result get_incr_number(units *res, unsigned char); + +int get_vunits(vunits *res, unsigned char si, vunits prev_value) +{ + units v; + switch (get_incr_number(&v, si)) { + case BAD: + return 0; + case ABSOLUTE: + *res = v; + break; + case INCREMENT: + *res = prev_value + v; + break; + case DECREMENT: + *res = prev_value - v; + break; + default: + assert(0 == "unhandled switch case returned by get_incr_number()"); + } + return 1; +} + +int get_hunits(hunits *res, unsigned char si, hunits prev_value) +{ + units v; + switch (get_incr_number(&v, si)) { + case BAD: + return 0; + case ABSOLUTE: + *res = v; + break; + case INCREMENT: + *res = prev_value + v; + break; + case DECREMENT: + *res = prev_value - v; + break; + default: + assert(0 == "unhandled switch case returned by get_incr_number()"); + } + return 1; +} + +int get_number(units *res, unsigned char si, units prev_value) +{ + units v; + switch (get_incr_number(&v, si)) { + case BAD: + return 0; + case ABSOLUTE: + *res = v; + break; + case INCREMENT: + *res = prev_value + v; + break; + case DECREMENT: + *res = prev_value - v; + break; + default: + assert(0 == "unhandled switch case returned by get_incr_number()"); + } + return 1; +} + +int get_integer(int *res, int prev_value) +{ + units v; + switch (get_incr_number(&v, 0)) { + case BAD: + return 0; + case ABSOLUTE: + *res = v; + break; + case INCREMENT: + *res = prev_value + int(v); + break; + case DECREMENT: + *res = prev_value - int(v); + break; + default: + assert(0 == "unhandled switch case returned by get_incr_number()"); + } + return 1; +} + + +static incr_number_result get_incr_number(units *res, unsigned char si) +{ + if (!is_valid_expression_start()) + return BAD; + incr_number_result result = ABSOLUTE; + if (tok.ch() == '+') { + tok.next(); + result = INCREMENT; + } + else if (tok.ch() == '-') { + tok.next(); + result = DECREMENT; + } + if (is_valid_expression(res, si, false /* is_parenthesized */)) + return result; + else + return BAD; +} + +static bool is_valid_expression_start() +{ + while (tok.is_space()) + tok.next(); + if (tok.is_newline()) { + warning(WARN_MISSING, "numeric expression missing"); + return false; + } + if (tok.is_tab()) { + warning(WARN_TAB, "expected numeric expression, got %1", + tok.description()); + return false; + } + if (tok.is_right_brace()) { + warning(WARN_RIGHT_BRACE, "expected numeric expression, got right" + "brace escape sequence"); + return false; + } + return true; +} + +enum { OP_LEQ = 'L', OP_GEQ = 'G', OP_MAX = 'X', OP_MIN = 'N' }; + +#define SCALING_UNITS "icfPmnpuvMsz" + +static bool is_valid_term(units *v, int scaling_unit, + bool is_parenthesized, bool is_mandatory); + +static bool is_valid_expression(units *v, int scaling_unit, + bool is_parenthesized, + bool is_mandatory) +{ + int result = is_valid_term(v, scaling_unit, is_parenthesized, + is_mandatory); + while (result) { + if (is_parenthesized) + tok.skip(); + int op = tok.ch(); + switch (op) { + case '+': + case '-': + case '/': + case '*': + case '%': + case ':': + case '&': + tok.next(); + break; + case '>': + tok.next(); + if (tok.ch() == '=') { + tok.next(); + op = OP_GEQ; + } + else if (tok.ch() == '?') { + tok.next(); + op = OP_MAX; + } + break; + case '<': + tok.next(); + if (tok.ch() == '=') { + tok.next(); + op = OP_LEQ; + } + else if (tok.ch() == '?') { + tok.next(); + op = OP_MIN; + } + break; + case '=': + tok.next(); + if (tok.ch() == '=') + tok.next(); + break; + default: + return result; + } + units v2; + if (!is_valid_term(&v2, scaling_unit, is_parenthesized, + is_mandatory)) + return false; + int overflow = 0; + switch (op) { + case '<': + *v = *v < v2; + break; + case '>': + *v = *v > v2; + break; + case OP_LEQ: + *v = *v <= v2; + break; + case OP_GEQ: + *v = *v >= v2; + break; + case OP_MIN: + if (*v > v2) + *v = v2; + break; + case OP_MAX: + if (*v < v2) + *v = v2; + break; + case '=': + *v = *v == v2; + break; + case '&': + *v = *v > 0 && v2 > 0; + break; + case ':': + *v = *v > 0 || v2 > 0; + break; + case '+': + if (v2 < 0) { + if (*v < INT_MIN - v2) + overflow = 1; + } + else if (v2 > 0) { + if (*v > INT_MAX - v2) + overflow = 1; + } + if (overflow) { + error("addition overflow"); + return false; + } + *v += v2; + break; + case '-': + if (v2 < 0) { + if (*v > INT_MAX + v2) + overflow = 1; + } + else if (v2 > 0) { + if (*v < INT_MIN + v2) + overflow = 1; + } + if (overflow) { + error("subtraction overflow"); + return false; + } + *v -= v2; + break; + case '*': + if (v2 < 0) { + if (*v > 0) { + if ((unsigned)*v > -(unsigned)INT_MIN / -(unsigned)v2) + overflow = 1; + } + else if (-(unsigned)*v > INT_MAX / -(unsigned)v2) + overflow = 1; + } + else if (v2 > 0) { + if (*v > 0) { + if (*v > INT_MAX / v2) + overflow = 1; + } + else if (-(unsigned)*v > -(unsigned)INT_MIN / v2) + overflow = 1; + } + if (overflow) { + error("multiplication overflow"); + return false; + } + *v *= v2; + break; + case '/': + if (v2 == 0) { + error("division by zero"); + return false; + } + *v /= v2; + break; + case '%': + if (v2 == 0) { + error("modulus by zero"); + return false; + } + *v %= v2; + break; + default: + assert(0 == "unhandled switch case while processing operator"); + } + } + return result; +} + +static bool is_valid_term(units *v, int scaling_unit, + bool is_parenthesized, bool is_mandatory) +{ + int negative = 0; + for (;;) + if (is_parenthesized && tok.is_space()) + tok.next(); + else if (tok.ch() == '+') + tok.next(); + else if (tok.ch() == '-') { + tok.next(); + negative = !negative; + } + else + break; + unsigned char c = tok.ch(); + switch (c) { + case '|': + // | is not restricted to the outermost level + // tbl uses this + tok.next(); + if (!is_valid_term(v, scaling_unit, is_parenthesized, is_mandatory)) + return false; + int tem; + tem = (scaling_unit == 'v' + ? curdiv->get_vertical_position().to_units() + : curenv->get_input_line_position().to_units()); + if (tem >= 0) { + if (*v < INT_MIN + tem) { + error("numeric overflow"); + return false; + } + } + else { + if (*v > INT_MAX + tem) { + error("numeric overflow"); + return false; + } + } + *v -= tem; + if (negative) { + if (*v == INT_MIN) { + error("numeric overflow"); + return false; + } + *v = -*v; + } + return true; + case '(': + tok.next(); + c = tok.ch(); + if (c == ')') { + if (is_mandatory) + return false; + warning(WARN_SYNTAX, "empty parentheses"); + tok.next(); + *v = 0; + return true; + } + else if (c != 0 && strchr(SCALING_UNITS, c) != 0) { + tok.next(); + if (tok.ch() == ';') { + tok.next(); + scaling_unit = c; + } + else { + error("expected ';' after scaling unit, got %1", + tok.description()); + return false; + } + } + else if (c == ';') { + scaling_unit = 0; + tok.next(); + } + if (!is_valid_expression(v, scaling_unit, + true /* is_parenthesized */, is_mandatory)) + return false; + tok.skip(); + if (tok.ch() != ')') { + if (is_mandatory) + return false; + warning(WARN_SYNTAX, "expected ')', got %1", tok.description()); + } + else + tok.next(); + if (negative) { + if (*v == INT_MIN) { + error("numeric overflow"); + return false; + } + *v = -*v; + } + return true; + case '.': + *v = 0; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + *v = 0; + do { + if (*v > INT_MAX/10) { + error("numeric overflow"); + return false; + } + *v *= 10; + if (*v > INT_MAX - (int(c) - '0')) { + error("numeric overflow"); + return false; + } + *v += c - '0'; + tok.next(); + c = tok.ch(); + } while (csdigit(c)); + break; + case '/': + case '*': + case '%': + case ':': + case '&': + case '>': + case '<': + case '=': + warning(WARN_SYNTAX, "empty left operand to '%1' operator", c); + *v = 0; + return is_mandatory ? false : true; + default: + warning(WARN_NUMBER, "expected numeric expression, got %1", + tok.description()); + return false; + } + int divisor = 1; + if (tok.ch() == '.') { + tok.next(); + for (;;) { + c = tok.ch(); + if (!csdigit(c)) + break; + // we may multiply the divisor by 254 later on + if (divisor <= INT_MAX/2540 && *v <= (INT_MAX - 9)/10) { + *v *= 10; + *v += c - '0'; + divisor *= 10; + } + tok.next(); + } + } + int si = scaling_unit; + int do_next = 0; + if ((c = tok.ch()) != 0 && strchr(SCALING_UNITS, c) != 0) { + switch (scaling_unit) { + case 0: + warning(WARN_SCALE, "scaling unit invalid in context"); + break; + case 'z': + if (c != 'u' && c != 'z') { + warning(WARN_SCALE, "'%1' scaling unit invalid in context;" + " convert to 'z' or 'u'", c); + break; + } + si = c; + break; + case 'u': + si = c; + break; + default: + if (c == 'z') { + warning(WARN_SCALE, "'z' scaling unit invalid in context"); + break; + } + si = c; + break; + } + // Don't do tok.next() here because the next token might be \s, + // which would affect the interpretation of m. + do_next = 1; + } + switch (si) { + case 'i': + *v = scale(*v, units_per_inch, divisor); + break; + case 'c': + *v = scale(*v, units_per_inch*100, divisor*254); + break; + case 0: + case 'u': + if (divisor != 1) + *v /= divisor; + break; + case 'f': + *v = scale(*v, 65536, divisor); + break; + case 'p': + *v = scale(*v, units_per_inch, divisor*72); + break; + case 'P': + *v = scale(*v, units_per_inch, divisor*6); + break; + case 'm': + { + // Convert to hunits so that with -Tascii 'm' behaves as in nroff. + hunits em = curenv->get_size(); + *v = scale(*v, em.is_zero() ? hresolution : em.to_units(), + divisor); + } + break; + case 'M': + { + hunits em = curenv->get_size(); + *v = scale(*v, em.is_zero() ? hresolution : em.to_units(), + (divisor * 100)); + } + break; + case 'n': + { + // Convert to hunits so that with -Tascii 'n' behaves as in nroff. + hunits en = curenv->get_size() / 2; + *v = scale(*v, en.is_zero() ? hresolution : en.to_units(), + divisor); + } + break; + case 'v': + *v = scale(*v, curenv->get_vertical_spacing().to_units(), divisor); + break; + case 's': + while (divisor > INT_MAX/(sizescale*72)) { + divisor /= 10; + *v /= 10; + } + *v = scale(*v, units_per_inch, divisor*sizescale*72); + break; + case 'z': + *v = scale(*v, sizescale, divisor); + break; + default: + assert(0 == "unhandled switch case when processing scaling unit"); + } + if (do_next) + tok.next(); + if (negative) { + if (*v == INT_MIN) { + error("numeric overflow"); + return false; + } + *v = -*v; + } + return true; +} + +units scale(units n, units x, units y) +{ + assert(x >= 0 && y > 0); + if (x == 0) + return 0; + if (n >= 0) { + if (n <= INT_MAX/x) + return (n*x)/y; + } + else { + if (-(unsigned)n <= -(unsigned)INT_MIN/x) + return (n*x)/y; + } + double res = n*double(x)/double(y); + if (res > INT_MAX) { + error("numeric overflow"); + return INT_MAX; + } + else if (res < INT_MIN) { + error("numeric overflow"); + return INT_MIN; + } + return int(res); +} + +vunits::vunits(units x) +{ + // Don't depend on rounding direction when dividing negative integers. + if (vresolution == 1) + n = x; + else + n = (x < 0 + ? -((-x + (vresolution / 2) - 1) / vresolution) + : (x + (vresolution / 2) - 1) / vresolution); +} + +hunits::hunits(units x) +{ + // Don't depend on rounding direction when dividing negative integers. + if (hresolution == 1) + n = x; + else + n = (x < 0 + ? -((-x + (hresolution / 2) - 1) / hresolution) + : (x + (hresolution / 2) - 1) / hresolution); +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: |