670 lines
13 KiB
C++
670 lines
13 KiB
C++
// -*- C++ -*-
|
|
/* Copyright (C) 1989, 1990, 1991, 1992 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, 675 Mass Ave, Cambridge, MA 02139, USA. */
|
|
|
|
|
|
#include "troff.h"
|
|
#include "symbol.h"
|
|
#include "hvunits.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);
|
|
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;
|
|
}
|
|
|
|
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 "icPmnpuvMsz"
|
|
|
|
static int parse_term(units *v, int scale_indicator, int parenthesised);
|
|
|
|
static int parse_expr(units *v, int scale_indicator, int parenthesised)
|
|
{
|
|
int result = parse_term(v, scale_indicator, parenthesised);
|
|
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))
|
|
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;
|
|
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 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))
|
|
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 == ')') {
|
|
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))
|
|
return 0;
|
|
tok.skip();
|
|
if (tok.ch() != ')') {
|
|
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 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 '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);
|
|
}
|