/
niceSOFT
/
groff
Обзор
Документация
Войти
/
niceSOFT
/
groff
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/roff/troff/number.cpp
759 строк
19 KB
G. Branden Robinson
[troff]: Recast error diagnostic.
12 июл 2026, 16:09
12 июл 2026, 16:09
88a2fcf
Код
Авторство
О чём код?
/* Copyright 1989-2020 Free Software Foundation, Inc. 2021-2025 G. Branden Robinson Written by James Clark (jjc@jclark.com) This file is part of groff, the GNU roff typesetting system. 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/>. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <stdckdint.h> #include <stdio.h> // prerequisite of mtsm.h, searchpath.h #include <stack> // prerequisite of mtsm.h // libgroff #include "errarg.h" // prerequisite of troff.h #include "error.h" // prerequisite of troff.h #include "searchpath.h" // prerequisite of troff.h #include "symbol.h" // prerequisite of color.h #include "color.h" // prerequisite of env.h #include "cset.h" // csdigit() #include "stringclass.h" // prerequisite of mtsm.h // troff #include "troff.h" // prerequisite of hvunits.h, token.h; units #include "hvunits.h" // prerequisite of div.h; hunits, vunits #include "dictionary.h" // prerequisite of request.h #include "env.h" // prerequisite of node.h; curenv #include "mtsm.h" // prerequisite of node.h #include "request.h" // prerequisite of node.h #include "node.h" // prerequisite of div.h #include "div.h" // curdiv #include "token.h" // tok const vunits V0; // zero in vertical units const hunits H0; // zero in horizontal units units hresolution = 1; units vresolution = 1; units units_per_inch; int sizescale; // subdivisions per point static bool is_valid_expression(units * /* u */, unsigned char /* scaling_unit */, // TODO: grochar bool /* is_parenthesized */, bool /* is_mandatory */ = false); static bool is_valid_expression_start(); bool read_vunits(vunits *res, unsigned char si) // TODO: grochar { if (!is_valid_expression_start()) return false; units x; if (is_valid_expression(&x, si, false /* is_parenthesized */)) { *res = vunits(x); return true; } return false; } bool read_hunits(hunits *res, unsigned char si) // TODO: grochar { if (!is_valid_expression_start()) return false; units x; if (is_valid_expression(&x, si, false /* is_parenthesized */)) { *res = hunits(x); return true; } return false; } bool read_measurement(units *res, unsigned char si, // TODO: grochar bool is_mandatory) { if (!is_valid_expression_start()) return false; units x; if (is_valid_expression(&x, si, false /* is_parenthesized */, is_mandatory)) { *res = x; return true; } return false; } bool read_integer(int *res) { if (!is_valid_expression_start()) return false; units x; if (is_valid_expression(&x, 0 /* dimensionless */, false /* is_parenthesized */)) { *res = x; return true; } return false; } // A "crement" is a *roff numeric expression that accepts an optional // leading sign (plus or minus) to alter a value relative to its // existing magnitude. enum crement { INVALID, ASSIGN, INCREMENT, DECREMENT }; static crement read_crement(units * /* res */, unsigned char /* si */); // TODO: grochar bool read_vunits(vunits *res, unsigned char si, // TODO: grochar vunits prev_value) { units v; // Use a primitive temporary because having the ckd macros store to // &(res->n) requires `friend` access and produces wrong results. int i; switch (read_crement(&v, si)) { case INVALID: return false; case ASSIGN: *res = v; break; case INCREMENT: if (ckd_add(&i, prev_value.to_units(), v)) warning(WARN_RANGE, "integer incrementation saturated"); *res = i; break; case DECREMENT: if (ckd_sub(&i, prev_value.to_units(), v)) warning(WARN_RANGE, "integer decrementation saturated"); *res = i; break; default: assert(0 == "unhandled case in read_vunits()"); } return true; } bool read_hunits(hunits *res, unsigned char si, // TODO: grochar hunits prev_value) { units h; // Use a primitive temporary because having the ckd macros store to // &(res->n) requires `friend` access and produces wrong results. int i; switch (read_crement(&h, si)) { case INVALID: return false; case ASSIGN: *res = h; break; case INCREMENT: if (ckd_add(&i, prev_value.to_units(), h)) warning(WARN_RANGE, "integer incrementation saturated"); *res = i; break; case DECREMENT: if (ckd_sub(&i, prev_value.to_units(), h)) warning(WARN_RANGE, "integer decrementation saturated"); *res = i; break; default: assert(0 == "unhandled case in read_hunits()"); } return true; } bool read_measurement_crement(units *res, unsigned char si, // TODO: grochar units operand) { units u; switch (read_crement(&u, si)) { case INVALID: return false; case ASSIGN: *res = u; break; case INCREMENT: if (ckd_add(res, operand, u)) warning(WARN_RANGE, "integer incrementation saturated"); break; case DECREMENT: if (ckd_sub(res, operand, u)) warning(WARN_RANGE, "integer decrementation saturated"); break; default: assert(0 == "unhandled case in read_measurement()"); } return true; } bool read_integer_crement(int *res, int operand) { units i; switch (read_crement(&i, 0)) { case INVALID: return false; case ASSIGN: *res = i; break; case INCREMENT: if (ckd_add(res, operand, i)) warning(WARN_RANGE, "integer incrementation saturated"); break; case DECREMENT: if (ckd_sub(res, operand, i)) warning(WARN_RANGE, "integer decrementation saturated"); break; default: assert(0 == "unhandled case in read_integer()"); } return true; } static crement read_crement(units *res, unsigned char si) // TODO: grochar { if (!is_valid_expression_start()) return INVALID; crement result = ASSIGN; if (tok.ch() == int('+')) { // TODO: grochar tok.next(); result = INCREMENT; } else if (tok.ch() == int('-')) { // TODO: grochar tok.next(); result = DECREMENT; } if (is_valid_expression(res, si, false /* is_parenthesized */)) return result; return INVALID; } // TODO: This is a pretty crude test: it doesn't check the first // character of a putative numeric expression to see if it makes any // sense. Can we be more scrupulous at our call sites and turn this // into a simple `inline` assert(3)ion? static bool is_valid_expression_start() { tok.skip_spaces(); if (tok.is_newline()) { warning(WARN_MISSING, "numeric expression missing"); return false; } return true; } enum { OP_LEQ = 'L', OP_GEQ = 'G', OP_MAX = 'X', OP_MIN = 'N' }; static const string valid_scaling_units("icfPmnpuvMsz"); static bool is_valid_term(units * /* u */, unsigned char /* scaling_unit */, // TODO: grochar bool /* is_parenthesized */, bool /* is_mandatory */); static bool is_valid_expression(units *u, unsigned char scaling_unit, // TODO: grochar bool is_parenthesized, bool is_mandatory) { int result = is_valid_term(u, scaling_unit, is_parenthesized, is_mandatory); while (result) { if (is_parenthesized) { if (want_att_compat && tok.is_space()) warning(WARN_SYNTAX, "%1 within a numeric expression" " is not portable to AT&T troff", tok.description()); // See `token::skip_spaces()` in "input.cpp". while (tok.is_space()) tok.next(); } int op = tok.ch();// safely compares to char literals; TODO: grochar switch (op) { case int('+'): // TODO: grochar case int('-'): // TODO: grochar case int('/'): // TODO: grochar case int('*'): // TODO: grochar case int('%'): // TODO: grochar case int(':'): // TODO: grochar case int('&'): // TODO: grochar tok.next(); break; case int('>'): // TODO: grochar tok.next(); if (tok.ch() == int('=')) { // TODO: grochar tok.next(); op = OP_GEQ; } else if (tok.ch() == int('?')) { // TODO: grochar tok.next(); op = OP_MAX; } break; case int('<'): // TODO: grochar tok.next(); if (tok.ch() == int('=')) { // TODO: grochar tok.next(); op = OP_LEQ; } else if (tok.ch() == int('?')) { // TODO: grochar tok.next(); op = OP_MIN; } break; case int('='): // TODO: grochar tok.next(); if (tok.ch() == int('=')) // TODO: grochar tok.next(); break; default: return result; } units u2; if (!is_valid_term(&u2, scaling_unit, is_parenthesized, is_mandatory)) return false; switch (op) { case int('<'): // TODO: grochar *u = *u < u2; break; case int('>'): // TODO: grochar *u = *u > u2; break; case OP_LEQ: *u = *u <= u2; break; case OP_GEQ: *u = *u >= u2; break; case OP_MIN: if (*u > u2) *u = u2; break; case OP_MAX: if (*u < u2) *u = u2; break; case int('='): // TODO: grochar *u = (*u == u2); break; case int('&'): // TODO: grochar *u = (*u > 0) && (u2 > 0); break; case int(':'): // TODO: grochar *u = (*u > 0) || (u2 > 0); break; case int('+'): // TODO: grochar if (ckd_add(u, *u, u2)) { warning(WARN_RANGE, "integer addition saturated"); return false; } break; case int('-'): // TODO: grochar if (ckd_sub(u, *u, u2)) { warning(WARN_RANGE, "integer subtraction saturated"); return false; } break; case int('*'): // TODO: grochar if (ckd_mul(u, *u, u2)) { warning(WARN_RANGE, "integer multiplication saturated"); return false; } break; case int('/'): // TODO: grochar if (0 == u2) { error("division by zero"); return false; } *u /= u2; break; case int('%'): // TODO: grochar if (0 == u2) { error("modulus by zero"); return false; } *u %= u2; break; default: assert(0 == "unhandled case of operator"); } } return result; } static bool is_valid_term(units *u, unsigned char scaling_unit, // TODO: grochar bool is_parenthesized, bool is_mandatory) { bool is_negative = false; bool is_overflowing = false; units saved_u = 0; // for use when reading an overlong number for (;;) if (is_parenthesized && tok.is_space()) tok.next(); else if (tok.ch() == int('+')) // TODO: grochar tok.next(); else if (tok.ch() == int('-')) { // TODO: grochar tok.next(); is_negative = !is_negative; } else if (tok.is_tab()) { warning(WARN_TAB, "ignoring numeric expression starting with %1", tok.description()); return false; } else break; int c = tok.ch(); // safely compares to char literals; TODO: grochar switch (c) { case int('|'): // TODO: grochar // | is not restricted to the outermost level // tbl uses this tok.next(); if (!is_valid_term(u, scaling_unit, is_parenthesized, is_mandatory)) return false; int tmp, position; position = (('v' == scaling_unit) ? curdiv->get_vertical_position().to_units() : curenv->get_input_line_position().to_units()); if (ckd_sub(&tmp, *u, position)) { tmp = INT_MAX; warning(WARN_RANGE, "integer value saturated"); } *u = tmp; if (is_negative) *u = -*u; return true; case int('('): // TODO: grochar tok.next(); c = tok.ch(); if (int(')') == c) { // TODO: grochar if (is_mandatory) return false; warning(WARN_SYNTAX, "empty parentheses"); tok.next(); *u = 0; return true; } else if ((c != 0) && valid_scaling_units.contains(c)) { tok.next(); if (tok.ch() == int(';')) { // TODO: grochar 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(u, scaling_unit, true /* is_parenthesized */, is_mandatory)) return false; tok.skip_spaces(); if (tok.ch() != int(')')) { // TODO: grochar if (is_mandatory) return false; warning(WARN_SYNTAX, "expected ')', got %1", tok.description()); } else tok.next(); if (is_negative) { // Why? Consider -(INT_MIN) in two's complement. if (ckd_mul(u, *u, -1)) warning(WARN_RANGE, "integer multiplication saturated"); } return true; case int('.'): // TODO: grochar *u = 0; break; case int('0'): // TODO: grochar case int('1'): // TODO: grochar case int('2'): // TODO: grochar case int('3'): // TODO: grochar case int('4'): // TODO: grochar case int('5'): // TODO: grochar case int('6'): // TODO: grochar case int('7'): // TODO: grochar case int('8'): // TODO: grochar case int('9'): // TODO: grochar *u = 0; do { if (!is_overflowing) { saved_u = *u; if (ckd_mul(u, *u, 10)) is_overflowing = true; if (ckd_add(u, *u, c - '0')) is_overflowing = true; if (is_overflowing) *u = saved_u; } // No `else` on overflow; consume and discard further digits. tok.next(); c = tok.ch(); } while (csdigit(c)); if (is_overflowing) warning(WARN_RANGE, "integer value saturated"); break; case int('/'): // TODO: grochar case int('*'): // TODO: grochar case int('%'): // TODO: grochar case int(':'): // TODO: grochar case int('&'): // TODO: grochar case int('>'): // TODO: grochar case int('<'): // TODO: grochar case int('='): // TODO: grochar warning(WARN_SYNTAX, "empty left operand to '%1' operator", char(c)); *u = 0; return !is_mandatory; default: error("numeric expression containing %1 is invalid", tok.description()); return false; } int divisor = 1; if (tok.ch() == int('.')) { // TODO: grochar tok.next(); for (;;) { c = tok.ch(); if (!csdigit(c)) break; // we may multiply the divisor by 254 later on if ((divisor <= (INT_MAX / 2540)) && (*u <= ((INT_MAX - 9) / 10))) { *u *= 10; *u += c - '0'; divisor *= 10; } tok.next(); } } unsigned char si = scaling_unit; // TODO: grochar bool do_next = false; if (((c = tok.ch()) != 0U) && valid_scaling_units.contains(c)) { switch (scaling_unit) { case int(0U): // TODO: grochar; null character, not digit zero warning(WARN_SCALE, "a scaling unit is not valid in this context" " (got '%1')", char(c)); break; case int('f'): // TODO: grochar if (c != int('f') && c != int('u')) { // TODO: grochar warning(WARN_SCALE, "'%1' scaling unit invalid in this context;" " use 'f' or 'u'", char(c)); break; } si = c; break; case int('z'): // TODO: grochar if (c != int('u') && c != int('z') && c != int('p') && c != int('s')) { // TODO: grochar warning(WARN_SCALE, "'%1' scaling unit invalid in this context;" " use 'z', 'p', 's', or 'u'", char(c)); break; } si = c; break; case int('u'): // TODO: grochar si = c; break; default: if (int('z') == c) { // TODO: grochar warning(WARN_SCALE, "'z' scaling unit invalid in this 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 = true; } switch (si) { case int('i'): // TODO: grochar *u = scale(*u, units_per_inch, divisor); break; case int('c'): // TODO: grochar *u = scale(*u, (units_per_inch * 100), (divisor * 254)); break; case int(0): // TODO: grochar; null character, not digit zero case int('u'): // TODO: grochar if (divisor != 1) *u /= divisor; break; case int('f'): // TODO: grochar *u = scale(*u, 65536, divisor); break; case int('p'): // TODO: grochar *u = scale(*u, units_per_inch, divisor * 72); break; case int('P'): // TODO: grochar *u = scale(*u, units_per_inch, divisor * 6); break; case int('m'): // TODO: grochar { // Convert to hunits so that with -Tascii 'm' behaves as in nroff. hunits em = curenv->get_size(); *u = scale(*u, em.is_zero() ? hresolution : em.to_units(), divisor); } break; case int('M'): // TODO: grochar { hunits em = curenv->get_size(); *u = scale(*u, em.is_zero() ? hresolution : em.to_units(), (divisor * 100)); } break; case int('n'): // TODO: grochar { // Convert to hunits so that with -Tascii 'n' behaves as in nroff. hunits en = curenv->get_size() / 2; *u = scale(*u, en.is_zero() ? hresolution : en.to_units(), divisor); } break; case int('v'): // TODO: grochar *u = scale(*u, curenv->get_vertical_spacing().to_units(), divisor); break; case int('s'): // TODO: grochar while (divisor > INT_MAX / (sizescale * 72)) { divisor /= 10; *u /= 10; } *u = scale(*u, units_per_inch, divisor * sizescale * 72); break; case int('z'): // TODO: grochar *u = scale(*u, sizescale, divisor); break; default: assert(0 == "unhandled case of scaling unit"); } if (do_next) tok.next(); if (is_negative) { if (ckd_mul(u, *u, -1)) warning(WARN_RANGE, "integer multiplication saturated"); } return true; } units scale(units n, units x, units y) { assert(x >= 0); assert(y > 0); if (0 == x) return 0; if (n >= 0) { if (n <= (INT_MAX / x)) return ((n * x) / y); } else { if ((-(unsigned int)(n)) <= ((-(unsigned int)(INT_MIN)) / x)) return ((n * x) / y); } double res = n * double(x) / double(y); if (res > INT_MAX) { warning(WARN_RANGE, "integer value saturated"); return INT_MAX; } else if (res < INT_MIN) { warning(WARN_RANGE, "integer value saturated"); return INT_MIN; } return units(res); } vunits::vunits(units x) { if (1 == vresolution) n = x; else { // Don't depend on rounding direction when dividing neg integers. int vcrement = (vresolution / 2) - 1; bool is_overflowing = false; if (x < 0) { if (ckd_add(&n, -x, vcrement)) is_overflowing = true; n = -n; } else { if (ckd_add(&n, x, vcrement)) is_overflowing = true; } if (is_overflowing) { if (x < 0) { warning(WARN_RANGE, "integer value saturated"); n = INT_MIN; } else { warning(WARN_RANGE, "integer value saturated"); n = INT_MAX; } } n /= vresolution; } } hunits::hunits(units x) { if (1 == hresolution) n = x; else { // Don't depend on rounding direction when dividing neg integers. int hcrement = (hresolution / 2) - 1; bool is_overflowing = false; if (x < 0) { if (ckd_add(&n, -x, hcrement)) is_overflowing = true; n = -n; } else { if (ckd_add(&n, x, hcrement)) is_overflowing = true; } if (is_overflowing) { if (x < 0) { warning(WARN_RANGE, "integer value saturated"); n = INT_MIN; } else { warning(WARN_RANGE, "integer value saturated"); n = INT_MAX; } } n /= hresolution; } } // Local Variables: // fill-column: 72 // mode: C++ // End: // vim: set cindent noexpandtab shiftwidth=2 textwidth=72: