// -*- C++ -*- /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 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 "dictionary.h" #include "token.h" #include "request.h" #include "reg.h" object_dictionary number_reg_dictionary(101); int reg::get_value(units * /*d*/) { return 0; } void reg::increment() { error("can't increment read-only register"); } void reg::decrement() { error("can't decrement read-only register"); } void reg::set_increment(units /*n*/) { error("can't auto increment read-only register"); } void reg::alter_format(char /*f*/, int /*w*/) { error("can't alter format of read-only register"); } const char *reg::get_format() { return "0"; } void reg::set_value(units /*n*/) { error("can't write read-only register"); } general_reg::general_reg() : format('1'), width(0), inc(0) { } static char uppercase_array[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', }; static char lowercase_array[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', }; static const char *number_value_to_ascii(int value, char format, int width) { static char buf[128]; // must be at least 21 switch(format) { case '1': if (width <= 0) return i_to_a(value); else if (width > int(sizeof(buf) - 2)) sprintf(buf, "%.*d", int(sizeof(buf) - 2), int(value)); else sprintf(buf, "%.*d", width, int(value)); break; case 'i': case 'I': { char *p = buf; // troff uses z and w to represent 10000 and 5000 in Roman // numerals; I can find no historical basis for this usage const char *s = format == 'i' ? "zwmdclxvi" : "ZWMDCLXVI"; int n = int(value); if (n >= 40000 || n <= -40000) { error("magnitude of `%1' too big for i or I format", n); return i_to_a(n); } if (n == 0) { *p++ = '0'; *p = 0; break; } if (n < 0) { *p++ = '-'; n = -n; } while (n >= 10000) { *p++ = s[0]; n -= 10000; } for (int i = 1000; i > 0; i /= 10, s += 2) { int m = n/i; n -= m*i; switch (m) { case 3: *p++ = s[2]; /* falls through */ case 2: *p++ = s[2]; /* falls through */ case 1: *p++ = s[2]; break; case 4: *p++ = s[2]; *p++ = s[1]; break; case 8: *p++ = s[1]; *p++ = s[2]; *p++ = s[2]; *p++ = s[2]; break; case 7: *p++ = s[1]; *p++ = s[2]; *p++ = s[2]; break; case 6: *p++ = s[1]; *p++ = s[2]; break; case 5: *p++ = s[1]; break; case 9: *p++ = s[2]; *p++ = s[0]; } } *p = 0; break; } case 'a': case 'A': { int n = value; char *p = buf; if (n == 0) { *p++ = '0'; *p = 0; } else { if (n < 0) { n = -n; *p++ = '-'; } // this is a bit tricky while (n > 0) { int d = n % 26; if (d == 0) d = 26; n -= d; n /= 26; *p++ = format == 'a' ? lowercase_array[d - 1] : uppercase_array[d - 1]; } *p-- = 0; char *q = buf[0] == '-' ? buf + 1 : buf; while (q < p) { char temp = *q; *q = *p; *p = temp; --p; ++q; } } break; } default: assert(0); break; } return buf; } const char *general_reg::get_string() { units n; if (!get_value(&n)) return ""; return number_value_to_ascii(n, format, width); } void general_reg::increment() { int n; if (get_value(&n)) set_value(n + inc); } void general_reg::decrement() { int n; if (get_value(&n)) set_value(n - inc); } void general_reg::set_increment(units n) { inc = n; } void general_reg::alter_format(char f, int w) { format = f; width = w; } static const char *number_format_to_ascii(char format, int width) { static char buf[24]; if (format == '1') { if (width > 0) { int n = width; if (n > int(sizeof(buf)) - 1) n = int(sizeof(buf)) - 1; sprintf(buf, "%.*d", n, 0); return buf; } else return "0"; } else { buf[0] = format; buf[1] = '\0'; return buf; } } const char *general_reg::get_format() { return number_format_to_ascii(format, width); } class number_reg : public general_reg { units value; public: number_reg(); int get_value(units *); void set_value(units); }; number_reg::number_reg() : value(0) { } int number_reg::get_value(units *res) { *res = value; return 1; } void number_reg::set_value(units n) { value = n; } variable_reg::variable_reg(units *p) : ptr(p) { } void variable_reg::set_value(units n) { *ptr = n; } int variable_reg::get_value(units *res) { *res = *ptr; return 1; } void define_number_reg() { symbol nm = get_name(1); if (nm.is_null()) { skip_line(); return; } reg *r = (reg *)number_reg_dictionary.lookup(nm); units v; units prev_value; if (!r || !r->get_value(&prev_value)) prev_value = 0; if (get_number(&v, 'u', prev_value)) { if (r == 0) { r = new number_reg; number_reg_dictionary.define(nm, r); } r->set_value(v); if (tok.space() && has_arg() && get_number(&v, 'u')) r->set_increment(v); } skip_line(); } #if 0 void inline_define_reg() { token start; start.next(); if (!start.delimiter(1)) return; tok.next(); symbol nm = get_name(1); if (nm.is_null()) return; reg *r = (reg *)number_reg_dictionary.lookup(nm); if (r == 0) { r = new number_reg; number_reg_dictionary.define(nm, r); } units v; units prev_value; if (!r->get_value(&prev_value)) prev_value = 0; if (get_number(&v, 'u', prev_value)) { r->set_value(v); if (start != tok) { if (get_number(&v, 'u')) { r->set_increment(v); if (start != tok) warning(WARN_DELIM, "closing delimiter does not match"); } } } } #endif void set_number_reg(symbol nm, units n) { reg *r = (reg *)number_reg_dictionary.lookup(nm); if (r == 0) { r = new number_reg; number_reg_dictionary.define(nm, r); } r->set_value(n); } reg *lookup_number_reg(symbol nm) { reg *r = (reg *)number_reg_dictionary.lookup(nm); if (r == 0) { warning(WARN_REG, "number register `%1' not defined", nm.contents()); r = new number_reg; number_reg_dictionary.define(nm, r); } return r; } void alter_format() { symbol nm = get_name(1); if (nm.is_null()) { skip_line(); return; } reg *r = (reg *)number_reg_dictionary.lookup(nm); if (r == 0) { r = new number_reg; number_reg_dictionary.define(nm, r); } tok.skip(); char c = tok.ch(); if (csdigit(c)) { int n = 0; do { ++n; tok.next(); } while (csdigit(tok.ch())); r->alter_format('1', n); } else if (c == 'i' || c == 'I' || c == 'a' || c == 'A') r->alter_format(c); else if (tok.newline() || tok.eof()) warning(WARN_MISSING, "missing number register format"); else error("bad number register format (got %1)", tok.description()); skip_line(); } void remove_reg() { for (;;) { symbol s = get_name(); if (s.is_null()) break; number_reg_dictionary.remove(s); } skip_line(); } void alias_reg() { symbol s1 = get_name(1); if (!s1.is_null()) { symbol s2 = get_name(1); if (!s2.is_null()) { if (!number_reg_dictionary.alias(s1, s2)) warning(WARN_REG, "number register `%1' not defined", s2.contents()); } } skip_line(); } void rename_reg() { symbol s1 = get_name(1); if (!s1.is_null()) { symbol s2 = get_name(1); if (!s2.is_null()) number_reg_dictionary.rename(s1, s2); } skip_line(); } void print_number_regs() { object_dictionary_iterator iter(number_reg_dictionary); reg *r; symbol s; while (iter.get(&s, (object **)&r)) { assert(!s.is_null()); errprint("%1\t", s.contents()); const char *p = r->get_string(); if (p) errprint(p); errprint("\n"); } fflush(stderr); skip_line(); } void init_reg_requests() { init_request("rr", remove_reg); init_request("nr", define_number_reg); init_request("af", alter_format); init_request("aln", alias_reg); init_request("rnn", rename_reg); init_request("pnr", print_number_regs); }