// -*- C++ -*- /* Copyright (C) 1989, 1990, 1991, 1992, 2001, 2002, 2004 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 2, 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 groff; see the file COPYING. If not, write to the Free Software Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */ #include "troff.h" #include "hvunits.h" #include "stringclass.h" #include "mtsm.h" #include "env.h" #include "token.h" #include "div.h" vunits V0; hunits H0; int hresolution = 1; int vresolution = 1; int units_per_inch; int sizescale; static int parse_expr(units *v, int scale_indicator, int parenthesised, int rigid = 0); static int start_number(); int get_vunits(vunits *res, unsigned char si) { if (!start_number()) return 0; units x; if (parse_expr(&x, si, 0)) { *res = vunits(x); return 1; } else return 0; } int get_hunits(hunits *res, unsigned char si) { if (!start_number()) return 0; units x; if (parse_expr(&x, si, 0)) { *res = hunits(x); return 1; } else return 0; } // for \B int get_number_rigidly(units *res, unsigned char si) { if (!start_number()) return 0; units x; if (parse_expr(&x, si, 0, 1)) { *res = x; return 1; } else return 0; } int get_number(units *res, unsigned char si) { if (!start_number()) return 0; units x; if (parse_expr(&x, si, 0)) { *res = x; return 1; } else return 0; } int get_integer(int *res) { if (!start_number()) return 0; units x; if (parse_expr(&x, 0, 0)) { *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); } 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); } 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); } 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); } return 1; } static incr_number_result get_incr_number(units *res, unsigned char si) { if (!start_number()) return BAD; incr_number_result result = ABSOLUTE; if (tok.ch() == '+') { tok.next(); result = INCREMENT; } else if (tok.ch() == '-') { tok.next(); result = DECREMENT; } if (parse_expr(res, si, 0)) return result; else return BAD; } static int start_number() { while (tok.space()) tok.next(); if (tok.newline()) { warning(WARN_MISSING, "missing number"); return 0; } if (tok.tab()) { warning(WARN_TAB, "tab character where number expected"); return 0; } if (tok.right_brace()) { warning(WARN_RIGHT_BRACE, "`\\}' where number expected"); return 0; } return 1; } enum { OP_LEQ = 'L', OP_GEQ = 'G', OP_MAX = 'X', OP_MIN = 'N' }; #define SCALE_INDICATOR_CHARS "icfPmnpuvMsz" static int parse_term(units *v, int scale_indicator, int parenthesised, int rigid); static int parse_expr(units *v, int scale_indicator, int parenthesised, int rigid) { int result = parse_term(v, scale_indicator, parenthesised, rigid); while (result) { if (parenthesised) 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 (!parse_term(&v2, scale_indicator, parenthesised, rigid)) return 0; 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 0; } *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 0; } *v -= v2; break; case '*': if (v2 < 0) { if (*v > 0) { if (*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 0; } *v *= v2; break; case '/': if (v2 == 0) { error("division by zero"); return 0; } *v /= v2; break; case '%': if (v2 == 0) { error("modulus by zero"); return 0; } *v %= v2; break; default: assert(0); } } return result; } static int parse_term(units *v, int scale_indicator, int parenthesised, int rigid) { int negative = 0; for (;;) if (parenthesised && tok.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 (!parse_term(v, scale_indicator, parenthesised, rigid)) return 0; int tem; tem = (scale_indicator == '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 0; } } else { if (*v > INT_MAX + tem) { error("numeric overflow"); return 0; } } *v -= tem; if (negative) { if (*v == INT_MIN) { error("numeric overflow"); return 0; } *v = -*v; } return 1; case '(': tok.next(); c = tok.ch(); if (c == ')') { if (rigid) return 0; warning(WARN_SYNTAX, "empty parentheses"); tok.next(); *v = 0; return 1; } else if (c != 0 && strchr(SCALE_INDICATOR_CHARS, c) != 0) { tok.next(); if (tok.ch() == ';') { tok.next(); scale_indicator = c; } else { error("expected `;' after scale-indicator (got %1)", tok.description()); return 0; } } else if (c == ';') { scale_indicator = 0; tok.next(); } if (!parse_expr(v, scale_indicator, 1, rigid)) return 0; tok.skip(); if (tok.ch() != ')') { if (rigid) return 0; warning(WARN_SYNTAX, "missing `)' (got %1)", tok.description()); } else tok.next(); if (negative) { if (*v == INT_MIN) { error("numeric overflow"); return 0; } *v = -*v; } return 1; 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 0; } *v *= 10; if (*v > INT_MAX - (int(c) - '0')) { error("numeric overflow"); return 0; } *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"); *v = 0; return rigid ? 0 : 1; default: warning(WARN_NUMBER, "numeric expression expected (got %1)", tok.description()); return 0; } 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 = scale_indicator; int do_next = 0; if ((c = tok.ch()) != 0 && strchr(SCALE_INDICATOR_CHARS, c) != 0) { switch (scale_indicator) { case 'z': if (c != 'u' && c != 'z') { warning(WARN_SCALE, "only `z' and `u' scale indicators valid in this context"); break; } si = c; break; case 0: warning(WARN_SCALE, "scale indicator invalid in this context"); break; case 'u': si = c; break; default: if (c == 'z') { warning(WARN_SCALE, "`z' scale indicator 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 = 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); } if (do_next) tok.next(); if (negative) { if (*v == INT_MIN) { error("numeric overflow"); return 0; } *v = -*v; } return 1; } 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 the rounding direction for division of 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 the rounding direction for division of negative integers if (hresolution == 1) n = x; else n = (x < 0 ? -((-x + hresolution/2 - 1)/hresolution) : (x + hresolution/2 - 1)/hresolution); }