BSD 4_4 development
authorCSRG <csrg@ucbvax.Berkeley.EDU>
Sun, 2 Aug 1992 20:30:03 +0000 (12:30 -0800)
committerCSRG <csrg@ucbvax.Berkeley.EDU>
Sun, 2 Aug 1992 20:30:03 +0000 (12:30 -0800)
Work on file usr/src/contrib/groff-1.08/tfmtodit/tfmtodit.cc
Work on file usr/src/contrib/groff-1.08/refer/label.y
Work on file usr/src/contrib/groff-1.08/refer/token.h
Work on file usr/src/contrib/groff-1.08/refer/command.h
Work on file usr/src/contrib/groff-1.08/refer/command.cc
Work on file usr/src/contrib/groff-1.08/refer/token.cc

Synthesized-from: CSRG/cd3/4.4

usr/src/contrib/groff-1.08/refer/command.cc [new file with mode: 0644]
usr/src/contrib/groff-1.08/refer/command.h [new file with mode: 0644]
usr/src/contrib/groff-1.08/refer/label.y [new file with mode: 0644]
usr/src/contrib/groff-1.08/refer/token.cc [new file with mode: 0644]
usr/src/contrib/groff-1.08/refer/token.h [new file with mode: 0644]
usr/src/contrib/groff-1.08/tfmtodit/tfmtodit.cc [new file with mode: 0644]

diff --git a/usr/src/contrib/groff-1.08/refer/command.cc b/usr/src/contrib/groff-1.08/refer/command.cc
new file mode 100644 (file)
index 0000000..93b6cfe
--- /dev/null
@@ -0,0 +1,807 @@
+// -*- 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 "refer.h"
+#include "refid.h"
+#include "search.h"
+#include "command.h"
+
+cset cs_field_name = csalpha;
+
+class input_item {
+  input_item *next;
+  char *filename;
+  int first_lineno;
+  string buffer;
+  const char *ptr;
+  const char *end;
+public:
+  input_item(string &, const char *, int = 1);
+  ~input_item();
+  int get_char();
+  int peek_char();
+  void skip_char();
+  int get_location(const char **, int *);
+
+  friend class input_stack;
+};
+
+input_item::input_item(string &s, const char *fn, int ln)
+: filename(strsave(fn)), first_lineno(ln)
+{
+  buffer.move(s);
+  ptr = buffer.contents();
+  end = ptr + buffer.length();
+}
+
+input_item::~input_item()
+{
+  a_delete filename;
+}
+
+inline int input_item::peek_char()
+{
+  if (ptr >= end)
+    return EOF;
+  else
+    return (unsigned char)*ptr;
+}
+
+inline int input_item::get_char()
+{
+  if (ptr >= end)
+    return EOF;
+  else
+    return (unsigned char)*ptr++;
+}
+
+inline void input_item::skip_char()
+{
+  ptr++;
+}
+
+int input_item::get_location(const char **filenamep, int *linenop)
+{
+  *filenamep = filename;
+  if (ptr == buffer.contents())
+    *linenop = first_lineno;
+  else {
+    int ln = first_lineno;
+    const char *e = ptr - 1;
+    for (const char *p = buffer.contents(); p < e; p++)
+      if (*p == '\n')
+       ln++;
+    *linenop = ln;
+  }
+  return 1;
+}
+
+class input_stack {
+  static input_item *top;
+public:
+  static void init();
+  static int get_char();
+  static int peek_char();
+  static void skip_char() { top->skip_char(); }
+  static void push_file(const char *);
+  static void push_string(string &, const char *, int);
+  static void error(const char *format,
+                   const errarg &arg1 = empty_errarg,
+                   const errarg &arg2 = empty_errarg,
+                   const errarg &arg3 = empty_errarg);
+};
+
+input_item *input_stack::top = 0;
+
+void input_stack::init()
+{
+  while (top) {
+    input_item *tem = top;
+    top = top->next;
+    delete tem;
+  }
+}
+
+int input_stack::get_char()
+{
+  while (top) {
+    int c = top->get_char();
+    if (c >= 0)
+      return c;
+    input_item *tem = top;
+    top = top->next;
+    delete tem;
+  }
+  return -1;
+}
+
+int input_stack::peek_char()
+{
+  while (top) {
+    int c = top->peek_char();
+    if (c >= 0)
+      return c;
+    input_item *tem = top;
+    top = top->next;
+    delete tem;
+  }
+  return -1;
+}
+
+void input_stack::push_file(const char *fn)
+{
+  FILE *fp;
+  if (strcmp(fn, "-") == 0) {
+    fp = stdin;
+    fn = "<standard input>";
+  }
+  else {
+    errno = 0;
+    fp = fopen(fn, "r");
+    if (fp == 0) {
+      error("can't open `%1': %2", fn, strerror(errno));
+      return;
+    }
+  }
+  string buf;
+  int bol = 1;
+  int lineno = 1;
+  for (;;) {
+    int c = getc(fp);
+    if (bol && c == '.') {
+      // replace lines beginning with .R1 or .R2 with a blank line
+      c = getc(fp);
+      if (c == 'R') {
+       c = getc(fp);
+       if (c == '1' || c == '2') {
+         int cc = c;
+         c = getc(fp);
+         if (compatible_flag || c == ' ' || c == '\n' || c == EOF) {
+           while (c != '\n' && c != EOF)
+             c = getc(fp);
+         }
+         else {
+           buf += '.';
+           buf += 'R';
+           buf += cc;
+         }
+       }
+       else {
+         buf += '.';
+         buf += 'R';
+       }
+      }
+      else
+       buf += '.';
+    }
+    if (c == EOF)
+      break;
+    if (illegal_input_char(c))
+      error_with_file_and_line(fn, lineno,
+                              "illegal input character code %1", int(c));
+    else {
+      buf += c;
+      if (c == '\n') {
+       bol = 1;
+       lineno++;
+      }
+      else
+       bol = 0;
+    }
+  }
+  if (fp != stdin)
+    fclose(fp);
+  if (buf.length() > 0 && buf[buf.length() - 1] != '\n')
+    buf += '\n';
+  input_item *it = new input_item(buf, fn);
+  it->next = top;
+  top = it;
+}
+
+void input_stack::push_string(string &s, const char *filename, int lineno)
+{
+  input_item *it = new input_item(s, filename, lineno);
+  it->next = top;
+  top = it;
+}
+
+void input_stack::error(const char *format, const errarg &arg1,
+                       const errarg &arg2, const errarg &arg3)
+{
+  const char *filename;
+  int lineno;
+  for (input_item *it = top; it; it = it->next)
+    if (it->get_location(&filename, &lineno)) {
+      error_with_file_and_line(filename, lineno, format, arg1, arg2, arg3);
+      return;
+    }
+  ::error(format, arg1, arg2, arg3);
+}
+
+void command_error(const char *format, const errarg &arg1,
+                  const errarg &arg2, const errarg &arg3)
+{
+  input_stack::error(format, arg1, arg2, arg3);
+}
+
+// # not recognized in ""
+// \<newline> is recognized in ""
+// # does not conceal newline
+// if missing closing quote, word extends to end of line
+// no special treatment of \ other than before newline
+// \<newline> not recognized after #
+// ; allowed as alternative to newline
+// ; not recognized in ""
+// don't clear word_buffer; just append on
+// return -1 for EOF, 0 for newline, 1 for word
+
+int get_word(string &word_buffer)
+{
+  int c = input_stack::get_char();
+  for (;;) {
+    if (c == '#') {
+      do {
+       c = input_stack::get_char();
+      } while (c != '\n' && c != EOF);
+      break;
+    }
+    if (c == '\\' && input_stack::peek_char() == '\n')
+      input_stack::skip_char();
+    else if (c != ' ' && c != '\t')
+      break;
+    c = input_stack::get_char();
+  }
+  if (c == EOF)
+    return -1;
+  if (c == '\n' || c == ';')
+    return 0;
+  if (c == '"') {
+    for (;;) {
+      c = input_stack::peek_char();
+      if (c == EOF || c == '\n')
+       break;
+      input_stack::skip_char();
+      if (c == '"') {
+       int d = input_stack::peek_char();
+       if (d == '"')
+         input_stack::skip_char();
+       else
+         break;
+      }
+      else if (c == '\\') {
+       int d = input_stack::peek_char();
+       if (d == '\n')
+         input_stack::skip_char();
+       else
+         word_buffer += '\\';
+      }
+      else
+       word_buffer += c;
+    }
+    return 1;
+  }
+  word_buffer += c;
+  for (;;) {
+    c = input_stack::peek_char();
+    if (c == ' ' || c == '\t' || c == '\n' || c == '#' || c == ';')
+      break;
+    input_stack::skip_char();
+    if (c == '\\') {
+      int d = input_stack::peek_char();
+      if (d == '\n')
+       input_stack::skip_char();
+      else
+       word_buffer += '\\';
+    }
+    else
+      word_buffer += c;
+  }
+  return 1;
+}
+
+union argument {
+  const char *s;
+  int n;
+};
+
+// This is for debugging.
+
+static void echo_command(int argc, argument *argv)
+{
+  for (int i = 0; i < argc; i++)
+    fprintf(stderr, "%s\n", argv[i].s);
+}
+
+static void include_command(int argc, argument *argv)
+{
+  assert(argc == 1);
+  input_stack::push_file(argv[0].s);
+}
+
+static void capitalize_command(int argc, argument *argv)
+{
+  if (argc > 0)
+    capitalize_fields = argv[0].s;
+  else
+    capitalize_fields.clear();
+}
+
+static void accumulate_command(int, argument *)
+{
+  accumulate = 1;
+}
+
+static void no_accumulate_command(int, argument *)
+{
+  accumulate = 0;
+}
+
+static void move_punctuation_command(int, argument *)
+{
+  move_punctuation = 1;
+}
+
+static void no_move_punctuation_command(int, argument *)
+{
+  move_punctuation = 0;
+}
+
+static void sort_command(int argc, argument *argv)
+{
+  if (argc == 0)
+    sort_fields = "AD";
+  else
+    sort_fields = argv[0].s;
+  accumulate = 1;
+}
+
+static void no_sort_command(int, argument *)
+{
+  sort_fields.clear();
+}
+
+static void articles_command(int argc, argument *argv)
+{
+  articles.clear();
+  int i;
+  for (i = 0; i < argc; i++) {
+    articles += argv[i].s;
+    articles += '\0';
+  }
+  int len = articles.length();
+  for (i = 0; i < len; i++)
+    articles[i] = cmlower(articles[i]);
+}
+
+static void database_command(int argc, argument *argv)
+{
+  for (int i = 0; i < argc; i++)
+    database_list.add_file(argv[i].s);
+}
+
+static void default_database_command(int, argument *)
+{
+  search_default = 1;
+}
+
+static void no_default_database_command(int, argument *)
+{
+  search_default = 0;
+}
+
+static void bibliography_command(int argc, argument *argv)
+{
+  const char *saved_filename = current_filename;
+  int saved_lineno = current_lineno;
+  int saved_label_in_text = label_in_text;
+  label_in_text = 0;
+  if (!accumulate)
+    fputs(".]<\n", stdout);
+  for (int i = 0; i < argc; i++)
+    do_bib(argv[i].s);
+  if (accumulate)
+    output_references();
+  else
+    fputs(".]>\n", stdout);
+  current_filename = saved_filename;
+  current_lineno = saved_lineno;
+  label_in_text = saved_label_in_text;
+}
+
+static void annotate_command(int argc, argument *argv)
+{
+  if (argc > 0)
+    annotation_field = argv[0].s[0];
+  else
+    annotation_field = 'X';
+  if (argc == 2)
+    annotation_macro = argv[1].s;
+  else
+    annotation_macro = "AP";
+}
+
+static void no_annotate_command(int, argument *)
+{
+  annotation_macro.clear();
+  annotation_field = -1;
+}
+
+static void reverse_command(int, argument *argv)
+{
+  reverse_fields = argv[0].s;
+}
+
+static void no_reverse_command(int, argument *)
+{
+  reverse_fields.clear();
+}
+
+static void abbreviate_command(int argc, argument *argv)
+{
+  abbreviate_fields = argv[0].s;
+  period_before_initial = argc > 1 ? argv[1].s : ". ";
+  period_before_last_name = argc > 2 ? argv[2].s : ". ";
+  period_before_other = argc > 3 ? argv[3].s : ". ";
+  period_before_hyphen = argc > 4 ? argv[4].s : ".";
+}
+
+static void no_abbreviate_command(int, argument *)
+{
+  abbreviate_fields.clear();
+}
+
+string search_ignore_fields;
+
+static void search_ignore_command(int argc, argument *argv)
+{
+  if (argc > 0)
+    search_ignore_fields = argv[0].s;
+  else
+    search_ignore_fields = "XYZ";
+  search_ignore_fields += '\0';
+  linear_ignore_fields = search_ignore_fields.contents();
+}
+
+static void no_search_ignore_command(int, argument *)
+{
+  linear_ignore_fields = "";
+}
+
+static void search_truncate_command(int argc, argument *argv)
+{
+  if (argc > 0)
+    linear_truncate_len = argv[0].n;
+  else
+    linear_truncate_len = 6;
+}
+
+static void no_search_truncate_command(int, argument *)
+{
+  linear_truncate_len = -1;
+}
+
+static void discard_command(int argc, argument *argv)
+{
+  if (argc == 0)
+    discard_fields = "XYZ";
+  else
+    discard_fields = argv[0].s;
+  accumulate = 1;
+}
+
+static void no_discard_command(int, argument *)
+{
+  discard_fields.clear();
+}
+
+static void label_command(int, argument *argv)
+{
+  set_label_spec(argv[0].s);
+}
+
+static void abbreviate_label_ranges_command(int argc, argument *argv)
+{
+  abbreviate_label_ranges = 1;
+  label_range_indicator = argc > 0 ? argv[0].s : "-";
+}
+
+static void no_abbreviate_label_ranges_command(int, argument *)
+{
+  abbreviate_label_ranges = 0;
+}
+
+static void label_in_reference_command(int, argument *)
+{
+  label_in_reference = 1;
+}
+
+static void no_label_in_reference_command(int, argument *)
+{
+  label_in_reference = 0;
+}
+
+static void label_in_text_command(int, argument *)
+{
+  label_in_text = 1;
+}
+
+static void no_label_in_text_command(int, argument *)
+{
+  label_in_text = 0;
+}
+
+static void sort_adjacent_labels_command(int, argument *)
+{
+  sort_adjacent_labels = 1;
+}
+
+static void no_sort_adjacent_labels_command(int, argument *)
+{
+  sort_adjacent_labels = 0;
+}
+
+static void date_as_label_command(int argc, argument *argv)
+{
+  if (set_date_label_spec(argc > 0 ? argv[0].s : "D%a*"))
+    date_as_label = 1;
+}
+
+static void no_date_as_label_command(int, argument *)
+{
+  date_as_label = 0;
+}
+
+static void short_label_command(int, argument *argv)
+{
+  if (set_short_label_spec(argv[0].s))
+    short_label_flag = 1;
+}
+
+static void no_short_label_command(int, argument *)
+{
+  short_label_flag = 0;
+}
+
+static void compatible_command(int, argument *)
+{
+  compatible_flag = 1;
+}
+
+static void no_compatible_command(int, argument *)
+{
+  compatible_flag = 0;
+}
+
+static void join_authors_command(int argc, argument *argv)
+{
+  join_authors_exactly_two = argv[0].s;
+  join_authors_default = argc > 1 ? argv[1].s : argv[0].s;
+  join_authors_last_two = argc == 3 ? argv[2].s : argv[0].s;
+}
+
+static void bracket_label_command(int, argument *argv)
+{
+  pre_label = argv[0].s;
+  post_label = argv[1].s;
+  sep_label = argv[2].s;
+}
+
+static void separate_label_second_parts_command(int, argument *argv)
+{
+  separate_label_second_parts = argv[0].s;
+}
+
+static void et_al_command(int argc, argument *argv)
+{
+  et_al = argv[0].s;
+  et_al_min_elide = argv[1].n;
+  if (et_al_min_elide < 1)
+    et_al_min_elide = 1;
+  et_al_min_total = argc >= 3 ? argv[2].n : 0;
+}
+
+static void no_et_al_command(int, argument *)
+{
+  et_al.clear();
+  et_al_min_elide = 0;
+}
+
+typedef void (*command_t)(int, argument *);
+
+/* arg_types is a string describing the numbers and types of arguments.
+s means a string, i means an integer, f is a list of fields, F is
+a single field,
+? means that the previous argument is optional, * means that the
+previous argument can occur any number of times. */
+
+struct {
+  const char *name;
+  command_t func;
+  const char *arg_types;
+} command_table[] = {
+  "include", include_command, "s",
+  "echo", echo_command, "s*",
+  "capitalize", capitalize_command, "f?",
+  "accumulate", accumulate_command, "",
+  "no-accumulate", no_accumulate_command, "",
+  "move-punctuation", move_punctuation_command, "",
+  "no-move-punctuation", no_move_punctuation_command, "",
+  "sort", sort_command, "s?",
+  "no-sort", no_sort_command, "",
+  "articles", articles_command, "s*",
+  "database", database_command, "ss*",
+  "default-database", default_database_command, "",
+  "no-default-database", no_default_database_command, "",
+  "bibliography", bibliography_command, "ss*",
+  "annotate", annotate_command, "F?s?",
+  "no-annotate", no_annotate_command, "",
+  "reverse", reverse_command, "s",
+  "no-reverse", no_reverse_command, "",
+  "abbreviate", abbreviate_command, "ss?s?s?s?",
+  "no-abbreviate", no_abbreviate_command, "",
+  "search-ignore", search_ignore_command, "f?",
+  "no-search-ignore", no_search_ignore_command, "",
+  "search-truncate", search_truncate_command, "i?",
+  "no-search-truncate", no_search_truncate_command, "",
+  "discard", discard_command, "f?",
+  "no-discard", no_discard_command, "",
+  "label", label_command, "s",
+  "abbreviate-label-ranges", abbreviate_label_ranges_command, "s?",
+  "no-abbreviate-label-ranges", no_abbreviate_label_ranges_command, "",
+  "label-in-reference", label_in_reference_command, "",
+  "no-label-in-reference", no_label_in_reference_command, "",
+  "label-in-text", label_in_text_command, "",
+  "no-label-in-text", no_label_in_text_command, "",
+  "sort-adjacent-labels", sort_adjacent_labels_command, "",
+  "no-sort-adjacent-labels", no_sort_adjacent_labels_command, "",
+  "date-as-label", date_as_label_command, "s?",
+  "no-date-as-label", no_date_as_label_command, "",
+  "short-label", short_label_command, "s",
+  "no-short-label", no_short_label_command, "",
+  "compatible", compatible_command, "",
+  "no-compatible", no_compatible_command, "",
+  "join-authors", join_authors_command, "sss?",
+  "bracket-label", bracket_label_command, "sss",
+  "separate-label-second-parts", separate_label_second_parts_command, "s",
+  "et-al", et_al_command, "sii?",
+  "no-et-al", no_et_al_command, "",
+};
+
+static int check_args(const char *types, const char *name,
+                     int argc, argument *argv)
+{
+  int argno = 0;
+  while (*types) {
+    if (argc == 0) {
+      if (types[1] == '?')
+       break;
+      else if (types[1] == '*') {
+       assert(types[2] == '\0');
+       break;
+      }
+      else {
+       input_stack::error("missing argument for command `%1'", name);
+       return 0;
+      }
+    }
+    switch (*types) {
+    case 's':
+      break;
+    case 'i':
+      {
+       char *ptr;
+       long n = strtol(argv->s, &ptr, 10);
+       if ((n == 0 && ptr == argv->s)
+           || *ptr != '\0') {
+         input_stack::error("argument %1 for command `%2' must be an integer",
+                            argno + 1, name);
+         return 0;
+       }
+       argv->n = (int)n;
+       break;
+      }
+    case 'f':
+      {
+       for (const char *ptr = argv->s; *ptr != '\0'; ptr++)
+         if (!cs_field_name(*ptr)) {
+           input_stack::error("argument %1 for command `%2' must be a list of fields",
+                            argno + 1, name);
+           return 0;
+         }
+       break;
+      }
+    case 'F':
+      if (argv->s[0] == '\0' || argv->s[1] != '\0'
+         || !cs_field_name(argv->s[0])) {
+       input_stack::error("argument %1 for command `%2' must be a field name",
+                          argno + 1, name);
+       return 0;
+      }
+      break;
+    default:
+      assert(0);
+    }
+    if (types[1] == '?')
+      types += 2;
+    else if (types[1] != '*')
+      types += 1;
+    --argc;
+    ++argv;
+    ++argno;
+  }
+  if (argc > 0) {
+    input_stack::error("too many arguments for command `%1'", name);
+    return 0;
+  }
+  return 1;
+}
+
+static void execute_command(const char *name, int argc, argument *argv)
+{
+  for (int i = 0; i < sizeof(command_table)/sizeof(command_table[0]); i++)
+    if (strcmp(name, command_table[i].name) == 0) {
+      if (check_args(command_table[i].arg_types, name, argc, argv))
+       (*command_table[i].func)(argc, argv);
+      return;
+    }
+  input_stack::error("unknown command `%1'", name);
+}
+
+static void command_loop()
+{
+  string command;
+  for (;;) {
+    command.clear();
+    int res = get_word(command);
+    if (res != 1) {
+      if (res == 0)
+       continue;
+      break;
+    }
+    int argc = 0;
+    command += '\0';
+    while ((res = get_word(command)) == 1) {
+      argc++;
+      command += '\0';
+    }
+    argument *argv = new argument[argc];
+    const char *ptr = command.contents();
+    for (int i = 0; i < argc; i++)
+      argv[i].s = ptr = strchr(ptr, '\0') + 1;
+    execute_command(command.contents(), argc, argv);
+    a_delete argv;
+    if (res == -1)
+      break;
+  }
+}
+
+void process_commands(const char *file)
+{
+  input_stack::init();
+  input_stack::push_file(file);
+  command_loop();
+}
+
+void process_commands(string &s, const char *file, int lineno)
+{
+  input_stack::init();
+  input_stack::push_string(s, file, lineno);
+  command_loop();
+}
diff --git a/usr/src/contrib/groff-1.08/refer/command.h b/usr/src/contrib/groff-1.08/refer/command.h
new file mode 100644 (file)
index 0000000..2b977ea
--- /dev/null
@@ -0,0 +1,36 @@
+// -*- 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. */
+
+void process_commands(const char *file);
+void process_commands(string &s, const char *file, int lineno);
+
+extern int accumulate;
+extern int move_punctuation;
+extern int search_default;
+extern search_list database_list;
+extern int label_in_text;
+extern int label_in_reference;
+extern int sort_adjacent_labels;
+extern string pre_label;
+extern string post_label;
+extern string sep_label;
+
+extern void do_bib(const char *);
+extern void output_references();
diff --git a/usr/src/contrib/groff-1.08/refer/label.y b/usr/src/contrib/groff-1.08/refer/label.y
new file mode 100644 (file)
index 0000000..d18eb89
--- /dev/null
@@ -0,0 +1,1173 @@
+/* -*- 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 "refer.h"
+#include "refid.h"
+#include "ref.h"
+#include "token.h"
+
+int yylex();
+void yyerror(const char *);
+int yyparse();
+
+static const char *format_serial(char c, int n);
+
+struct label_info {
+  int start;
+  int length;
+  int count;
+  int total;
+  label_info(const string &);
+};
+
+label_info *lookup_label(const string &label);
+
+struct expression {
+  enum {
+    // Does the tentative label depend on the reference?
+    CONTAINS_VARIABLE = 01, 
+    CONTAINS_STAR = 02,
+    CONTAINS_FORMAT = 04,
+    CONTAINS_AT = 010
+  };
+  virtual ~expression() { }
+  virtual void evaluate(int, const reference &, string &,
+                       substring_position &) = 0;
+  virtual unsigned analyze() { return 0; }
+};
+
+class at_expr : public expression {
+public:
+  at_expr() { }
+  void evaluate(int, const reference &, string &, substring_position &);
+  unsigned analyze() { return CONTAINS_VARIABLE|CONTAINS_AT; }
+};
+
+class format_expr : public expression {
+  char type;
+  int width;
+  int first_number;
+public:
+  format_expr(char c, int w = 0, int f = 1)
+    : type(c), width(w), first_number(f) { }
+  void evaluate(int, const reference &, string &, substring_position &);
+  unsigned analyze() { return CONTAINS_FORMAT; }
+};
+
+class field_expr : public expression {
+  int number;
+  char name;
+public:
+  field_expr(char nm, int num) : name(nm), number(num) { }
+  void evaluate(int, const reference &, string &, substring_position &);
+  unsigned analyze() { return CONTAINS_VARIABLE; }
+};
+
+class literal_expr : public expression {
+  string s;
+public:
+  literal_expr(const char *ptr, int len) : s(ptr, len) { }
+  void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class unary_expr : public expression {
+protected:
+  expression *expr;
+public:
+  unary_expr(expression *e) : expr(e) { }
+  ~unary_expr() { delete expr; }
+  void evaluate(int, const reference &, string &, substring_position &) = 0;
+  unsigned analyze() { return expr ? expr->analyze() : 0; }
+};
+
+// This caches the analysis of an expression.
+
+class analyzed_expr : public unary_expr {
+  unsigned flags;
+public:
+  analyzed_expr(expression *);
+  void evaluate(int, const reference &, string &, substring_position &);
+  unsigned analyze() { return flags; }
+};
+
+class star_expr : public unary_expr {
+public:
+  star_expr(expression *e) : unary_expr(e) { }
+  void evaluate(int, const reference &, string &, substring_position &);
+  unsigned analyze() {
+    return ((expr ? (expr->analyze() & ~CONTAINS_VARIABLE) : 0)
+           | CONTAINS_STAR);
+  }
+};
+
+typedef void map_t(const char *, const char *, string &);
+
+class map_expr : public unary_expr {
+  map_t *func;
+public:
+  map_expr(expression *e, map_t *f) : unary_expr(e), func(f) { }
+  void evaluate(int, const reference &, string &, substring_position &);
+};
+  
+typedef const char *extractor_t(const char *, const char *, const char **);
+
+class extractor_expr : public unary_expr {
+  int part;
+  extractor_t *func;
+public:
+  enum { BEFORE = +1, MATCH = 0, AFTER = -1 };
+  extractor_expr(expression *e, extractor_t *f, int pt)
+    : unary_expr(e), func(f), part(pt) { }
+  void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class truncate_expr : public unary_expr {
+  int n;
+public:
+  truncate_expr(expression *e, int i) : n(i), unary_expr(e) { } 
+  void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class separator_expr : public unary_expr {
+public:
+  separator_expr(expression *e) : unary_expr(e) { }
+  void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class binary_expr : public expression {
+protected:
+  expression *expr1;
+  expression *expr2;
+public:
+  binary_expr(expression *e1, expression *e2) : expr1(e1), expr2(e2) { }
+  ~binary_expr() { delete expr1; delete expr2; }
+  void evaluate(int, const reference &, string &, substring_position &) = 0;
+  unsigned analyze() {
+    return (expr1 ? expr1->analyze() : 0) | (expr2 ? expr2->analyze() : 0);
+  }
+};
+
+class alternative_expr : public binary_expr {
+public:
+  alternative_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { }
+  void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class list_expr : public binary_expr {
+public:
+  list_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { }
+  void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class substitute_expr : public binary_expr {
+public:
+  substitute_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { }
+  void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class ternary_expr : public expression {
+protected:
+  expression *expr1;
+  expression *expr2;
+  expression *expr3;
+public:
+  ternary_expr(expression *e1, expression *e2, expression *e3)
+    : expr1(e1), expr2(e2), expr3(e3) { }
+  ~ternary_expr() { delete expr1; delete expr2; delete expr3; }
+  void evaluate(int, const reference &, string &, substring_position &) = 0;
+  unsigned analyze() {
+    return ((expr1 ? expr1->analyze() : 0)
+           | (expr2 ? expr2->analyze() : 0)
+           | (expr3 ? expr3->analyze() : 0));
+  }
+};
+
+class conditional_expr : public ternary_expr {
+public:
+  conditional_expr(expression *e1, expression *e2, expression *e3)
+    : ternary_expr(e1, e2, e3) { }
+  void evaluate(int, const reference &, string &, substring_position &);
+};
+
+static expression *parsed_label = 0;
+static expression *parsed_date_label = 0;
+static expression *parsed_short_label = 0;
+
+static expression *parse_result;
+
+string literals;
+
+%}
+
+%union {
+  int num;
+  expression *expr;
+  struct { int ndigits; int val; } dig;
+  struct { int start; int len; } str;
+}
+
+/* uppercase or lowercase letter */
+%token <num> TOKEN_LETTER
+/* literal characters */
+%token <str> TOKEN_LITERAL
+/* digit */
+%token <num> TOKEN_DIGIT
+
+%type <expr> conditional
+%type <expr> alternative
+%type <expr> list
+%type <expr> string
+%type <expr> substitute
+%type <expr> optional_conditional
+%type <num> number
+%type <dig> digits
+%type <num> optional_number
+%type <num> flag
+
+%%
+
+expr:
+       optional_conditional
+               { parse_result = ($1 ? new analyzed_expr($1) : 0); }
+       ;
+
+conditional:
+       alternative
+               { $$ = $1; }
+       | alternative '?' optional_conditional ':' conditional
+               { $$ = new conditional_expr($1, $3, $5); }
+       ;
+
+optional_conditional:
+       /* empty */
+               { $$ = 0; }
+       | conditional
+               { $$ = $1; }
+       ;
+
+alternative:
+       list
+               { $$ = $1; }
+       | alternative '|' list
+               { $$ = new alternative_expr($1, $3); }
+       | alternative '&' list
+               { $$ = new conditional_expr($1, $3, 0); }
+       ;       
+
+list:
+       substitute
+               { $$ = $1; }
+       | list substitute
+               { $$ = new list_expr($1, $2); }
+       ;
+
+substitute:
+       string
+               { $$ = $1; }
+       | substitute '~' string
+               { $$ = new substitute_expr($1, $3); }
+       ;
+
+string:
+       '@'
+               { $$ = new at_expr; }
+       | TOKEN_LITERAL
+               {
+                 $$ = new literal_expr(literals.contents() + $1.start,
+                                       $1.len);
+               }
+       | TOKEN_LETTER
+               { $$ = new field_expr($1, 0); }
+       | TOKEN_LETTER number
+               { $$ = new field_expr($1, $2 - 1); }
+       | '%' TOKEN_LETTER
+               {
+                 switch ($2) {
+                 case 'I':
+                 case 'i':
+                 case 'A':
+                 case 'a':
+                   $$ = new format_expr($2);
+                   break;
+                 default:
+                   command_error("unrecognized format `%1'", char($2));
+                   $$ = new format_expr('a');
+                   break;
+                 }
+               }
+       
+       | '%' digits
+               {
+                 $$ = new format_expr('0', $2.ndigits, $2.val);
+               }
+       | string '.' flag TOKEN_LETTER optional_number
+               {
+                 switch ($4) {
+                 case 'l':
+                   $$ = new map_expr($1, lowercase);
+                   break;
+                 case 'u':
+                   $$ = new map_expr($1, uppercase);
+                   break;
+                 case 'c':
+                   $$ = new map_expr($1, capitalize);
+                   break;
+                 case 'r':
+                   $$ = new map_expr($1, reverse_name);
+                   break;
+                 case 'a':
+                   $$ = new map_expr($1, abbreviate_name);
+                   break;
+                 case 'y':
+                   $$ = new extractor_expr($1, find_year, $3);
+                   break;
+                 case 'n':
+                   $$ = new extractor_expr($1, find_last_name, $3);
+                   break;
+                 default:
+                   $$ = $1;
+                   command_error("unknown function `%1'", char($4));
+                   break;
+                 }
+               }
+
+       | string '+' number
+               { $$ = new truncate_expr($1, $3); }
+       | string '-' number
+               { $$ = new truncate_expr($1, -$3); }
+       | string '*'
+               { $$ = new star_expr($1); }
+       | '(' optional_conditional ')'
+               { $$ = $2; }
+       | '<' optional_conditional '>'
+               { $$ = new separator_expr($2); }
+       ;
+
+optional_number:
+       /* empty */
+               { $$ = -1; }
+       | number
+               { $$ = $1; }
+       ;
+
+number:
+       TOKEN_DIGIT
+               { $$ = $1; }
+       | number TOKEN_DIGIT
+               { $$ = $1*10 + $2; }
+       ;
+
+digits:
+       TOKEN_DIGIT
+               { $$.ndigits = 1; $$.val = $1; }
+       | digits TOKEN_DIGIT
+               { $$.ndigits = $1.ndigits + 1; $$.val = $1.val*10 + $2; }
+       ;
+       
+      
+flag:
+       /* empty */
+               { $$ = 0; }
+       | '+'
+               { $$ = 1; }
+       | '-'
+               { $$ = -1; }
+       ;
+
+%%
+
+/* bison defines const to be empty unless __STDC__ is defined, which it
+isn't under cfront */
+
+#ifdef const
+#undef const
+#endif
+
+const char *spec_ptr;
+const char *spec_end;
+const char *spec_cur;
+
+int yylex()
+{
+  while (spec_ptr < spec_end && csspace(*spec_ptr))
+    spec_ptr++;
+  spec_cur = spec_ptr;
+  if (spec_ptr >= spec_end)
+    return 0;
+  unsigned char c = *spec_ptr++;
+  if (csalpha(c)) {
+    yylval.num = c;
+    return TOKEN_LETTER;
+  }
+  if (csdigit(c)) {
+    yylval.num = c - '0';
+    return TOKEN_DIGIT;
+  }
+  if (c == '\'') {
+    yylval.str.start = literals.length();
+    for (; spec_ptr < spec_end; spec_ptr++) {
+      if (*spec_ptr == '\'') {
+       if (++spec_ptr < spec_end && *spec_ptr == '\'')
+         literals += '\'';
+       else {
+         yylval.str.len = literals.length() - yylval.str.start;
+         return TOKEN_LITERAL;
+       }
+      }
+      else
+       literals += *spec_ptr;
+    }
+    yylval.str.len = literals.length() - yylval.str.start;
+    return TOKEN_LITERAL;
+  }
+  return c;
+}
+
+int set_label_spec(const char *label_spec)
+{
+  spec_cur = spec_ptr = label_spec;
+  spec_end = strchr(label_spec, '\0');
+  literals.clear();
+  if (yyparse())
+    return 0;
+  delete parsed_label;
+  parsed_label = parse_result;
+  return 1;
+}
+
+int set_date_label_spec(const char *label_spec)
+{
+  spec_cur = spec_ptr = label_spec;
+  spec_end = strchr(label_spec, '\0');
+  literals.clear();
+  if (yyparse())
+    return 0;
+  delete parsed_date_label;
+  parsed_date_label = parse_result;
+  return 1;
+}
+
+int set_short_label_spec(const char *label_spec)
+{
+  spec_cur = spec_ptr = label_spec;
+  spec_end = strchr(label_spec, '\0');
+  literals.clear();
+  if (yyparse())
+    return 0;
+  delete parsed_short_label;
+  parsed_short_label = parse_result;
+  return 1;
+}
+
+void yyerror(const char *message)
+{
+  if (spec_cur < spec_end)
+    command_error("label specification %1 before `%2'", message, spec_cur);
+  else
+    command_error("label specification %1 at end of string",
+                 message, spec_cur);
+}
+
+void at_expr::evaluate(int tentative, const reference &ref,
+                      string &result, substring_position &)
+{
+  if (tentative)
+    ref.canonicalize_authors(result);
+  else {
+    const char *end, *start = ref.get_authors(&end);
+    if (start)
+      result.append(start, end - start);
+  }
+}
+
+void format_expr::evaluate(int tentative, const reference &ref,
+                          string &result, substring_position &)
+{
+  if (tentative)
+    return;
+  const label_info *lp = ref.get_label_ptr();
+  int num = lp == 0 ? ref.get_number() : lp->count;
+  if (type != '0')
+    result += format_serial(type, num + 1);
+  else {
+    const char *ptr = itoa(num + first_number);
+    int pad = width - strlen(ptr);
+    while (--pad >= 0)
+      result += '0';
+    result += ptr;
+  }
+}
+
+static const char *format_serial(char c, int n)
+{
+  assert(n > 0);
+  static char buf[128]; // more than enough.
+  switch (c) {
+  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 = c == 'i' ? "zwmdclxvi" : "ZWMDCLXVI";
+      if (n >= 40000)
+       return itoa(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':
+    {
+      char *p = buf;
+      // this is derived from troff/reg.c
+      while (n > 0) {
+       int d = n % 26;
+       if (d == 0)
+         d = 26;
+       n -= d;
+       n /= 26;
+       *p++ = c + d - 1;       // ASCII dependent
+      }
+      *p-- = 0;
+      // Reverse it.
+      char *q = buf;
+      while (q < p) {
+       char temp = *q;
+       *q = *p;
+       *p = temp;
+       --p;
+       ++q;
+      }
+      break;
+    }
+  default:
+    assert(0);
+  }
+  return buf;
+}
+
+void field_expr::evaluate(int, const reference &ref,
+                         string &result, substring_position &)
+{
+  const char *end;
+  const char *start = ref.get_field(name, &end);
+  if (start) {
+    start = nth_field(number, start, &end);
+    if (start)
+      result.append(start, end - start);
+  }
+}
+
+void literal_expr::evaluate(int, const reference &,
+                           string &result, substring_position &)
+{
+  result += s;
+}
+
+analyzed_expr::analyzed_expr(expression *e)
+: unary_expr(e), flags(e ? e->analyze() : 0)
+{
+}
+
+void analyzed_expr::evaluate(int tentative, const reference &ref,
+                            string &result, substring_position &pos)
+{
+  if (expr)
+    expr->evaluate(tentative, ref, result, pos);
+}
+
+void star_expr::evaluate(int tentative, const reference &ref,
+                        string &result, substring_position &pos)
+{
+  const label_info *lp = ref.get_label_ptr();
+  if (!tentative
+      && (lp == 0 || lp->total > 1)
+      && expr)
+    expr->evaluate(tentative, ref, result, pos);
+}
+
+void separator_expr::evaluate(int tentative, const reference &ref,
+                             string &result, substring_position &pos)
+{
+  int start_length = result.length();
+  int is_first = pos.start < 0;
+  if (expr)
+    expr->evaluate(tentative, ref, result, pos);
+  if (is_first) {
+    pos.start = start_length;
+    pos.length = result.length() - start_length;
+  }
+}
+
+void map_expr::evaluate(int tentative, const reference &ref,
+                       string &result, substring_position &)
+{
+  if (expr) {
+    string temp;
+    substring_position temp_pos;
+    expr->evaluate(tentative, ref, temp, temp_pos);
+    (*func)(temp.contents(), temp.contents() + temp.length(), result);
+  }
+}
+
+void extractor_expr::evaluate(int tentative, const reference &ref,
+                             string &result, substring_position &)
+{
+  if (expr) {
+    string temp;
+    substring_position temp_pos;
+    expr->evaluate(tentative, ref, temp, temp_pos);
+    const char *end, *start = (*func)(temp.contents(),
+                                     temp.contents() + temp.length(),
+                                     &end);
+    switch (part) {
+    case BEFORE:
+      if (start)
+       result.append(temp.contents(), start - temp.contents());
+      else
+       result += temp;
+      break;
+    case MATCH:
+      if (start)
+       result.append(start, end - start);
+      break;
+    case AFTER:
+      if (start)
+       result.append(end, temp.contents() + temp.length() - end);
+      break;
+    default:
+      assert(0);
+    }
+  }
+}
+
+static void first_part(int len, const char *ptr, const char *end,
+                         string &result)
+{
+  for (;;) {
+    const char *token_start = ptr;
+    if (!get_token(&ptr, end))
+      break;
+    const token_info *ti = lookup_token(token_start, ptr);
+    int counts = ti->sortify_non_empty(token_start, ptr);
+    if (counts && --len < 0)
+      break;
+    if (counts || ti->is_accent())
+      result.append(token_start, ptr - token_start);
+  }
+}
+
+static void last_part(int len, const char *ptr, const char *end,
+                     string &result)
+{
+  const char *start = ptr;
+  int count = 0;
+  for (;;) {
+    const char *token_start = ptr;
+    if (!get_token(&ptr, end))
+      break;
+    const token_info *ti = lookup_token(token_start, ptr);
+    if (ti->sortify_non_empty(token_start, ptr))
+      count++;
+  }
+  ptr = start;
+  int skip = count - len;
+  if (skip > 0) {
+    for (;;) {
+      const char *token_start = ptr;
+      if (!get_token(&ptr, end))
+       assert(0);
+      const token_info *ti = lookup_token(token_start, ptr);
+      if (ti->sortify_non_empty(token_start, ptr) && --skip < 0) {
+       ptr = token_start;
+       break;
+      }
+    }
+  }
+  first_part(len, ptr, end, result);
+}
+
+void truncate_expr::evaluate(int tentative, const reference &ref,
+                            string &result, substring_position &)
+{
+  if (expr) {
+    string temp;
+    substring_position temp_pos;
+    expr->evaluate(tentative, ref, temp, temp_pos);
+    const char *start = temp.contents();
+    const char *end = start + temp.length();
+    if (n > 0)
+      first_part(n, start, end, result);
+    else if (n < 0)
+      last_part(-n, start, end, result);
+  }
+}
+
+void alternative_expr::evaluate(int tentative, const reference &ref,
+                               string &result, substring_position &pos)
+{
+  int start_length = result.length();
+  if (expr1)
+    expr1->evaluate(tentative, ref, result, pos);
+  if (result.length() == start_length && expr2)
+    expr2->evaluate(tentative, ref, result, pos);
+}
+
+void list_expr::evaluate(int tentative, const reference &ref,
+                        string &result, substring_position &pos)
+{
+  if (expr1)
+    expr1->evaluate(tentative, ref, result, pos);
+  if (expr2)
+    expr2->evaluate(tentative, ref, result, pos);
+}
+
+void substitute_expr::evaluate(int tentative, const reference &ref,
+                              string &result, substring_position &pos)
+{
+  int start_length = result.length();
+  if (expr1)
+    expr1->evaluate(tentative, ref, result, pos);
+  if (result.length() > start_length && result[result.length() - 1] == '-') {
+    // ought to see if pos covers the -
+    result.set_length(result.length() - 1);
+    if (expr2)
+      expr2->evaluate(tentative, ref, result, pos);
+  }
+}
+
+void conditional_expr::evaluate(int tentative, const reference &ref,
+                               string &result, substring_position &pos)
+{
+  string temp;
+  substring_position temp_pos;
+  if (expr1)
+    expr1->evaluate(tentative, ref, temp, temp_pos);
+  if (temp.length() > 0) {
+    if (expr2)
+      expr2->evaluate(tentative, ref, result, pos);
+  }
+  else {
+    if (expr3)
+      expr3->evaluate(tentative, ref, result, pos);
+  }
+}
+
+void reference::pre_compute_label()
+{
+  if (parsed_label != 0
+      && (parsed_label->analyze() & expression::CONTAINS_VARIABLE)) {
+    label.clear();
+    substring_position temp_pos;
+    parsed_label->evaluate(1, *this, label, temp_pos);
+    label_ptr = lookup_label(label);
+  }
+}
+
+void reference::compute_label()
+{
+  label.clear();
+  if (parsed_label)
+    parsed_label->evaluate(0, *this, label, separator_pos);
+  if (short_label_flag && parsed_short_label)
+    parsed_short_label->evaluate(0, *this, short_label, short_separator_pos);
+  if (date_as_label) {
+    string new_date;
+    if (parsed_date_label) {
+      substring_position temp_pos;
+      parsed_date_label->evaluate(0, *this, new_date, temp_pos);
+    }
+    set_date(new_date);
+  }
+  if (label_ptr)
+    label_ptr->count += 1;
+}
+
+void reference::immediate_compute_label()
+{
+  if (label_ptr)
+    label_ptr->total = 2;      // force use of disambiguator
+  compute_label();
+}
+
+int reference::merge_labels(reference **v, int n, label_type type,
+                           string &result)
+{
+  if (abbreviate_label_ranges)
+    return merge_labels_by_number(v, n, type, result);
+  else
+    return merge_labels_by_parts(v, n, type, result);
+}
+
+int reference::merge_labels_by_number(reference **v, int n, label_type type,
+                                     string &result)
+{
+  if (n <= 1)
+    return 0;
+  int num = get_number();
+  // Only merge three or more labels.
+  if (v[0]->get_number() != num + 1
+      || v[1]->get_number() != num + 2)
+    return 0;
+  for (int i = 2; i < n; i++)
+    if (v[i]->get_number() != num + i + 1)
+      break;
+  result = get_label(type);
+  result += label_range_indicator;
+  result += v[i - 1]->get_label(type);
+  return i;
+}
+
+const substring_position &reference::get_separator_pos(label_type type) const
+{
+  if (type == SHORT_LABEL && short_label_flag)
+    return short_separator_pos;
+  else
+    return separator_pos;
+}
+
+const string &reference::get_label(label_type type) const
+{
+  if (type == SHORT_LABEL && short_label_flag)
+    return short_label; 
+  else
+    return label;
+}
+
+int reference::merge_labels_by_parts(reference **v, int n, label_type type,
+                                    string &result)
+{
+  if (n <= 0)
+    return 0;
+  const string &lb = get_label(type);
+  const substring_position &sp = get_separator_pos(type);
+  if (sp.start < 0
+      || sp.start != v[0]->get_separator_pos(type).start 
+      || memcmp(lb.contents(), v[0]->get_label(type).contents(),
+               sp.start) != 0)
+    return 0;
+  result = lb;
+  int i = 0;
+  do {
+    result += separate_label_second_parts;
+    const substring_position &s = v[i]->get_separator_pos(type);
+    int sep_end_pos = s.start + s.length;
+    result.append(v[i]->get_label(type).contents() + sep_end_pos,
+                 v[i]->get_label(type).length() - sep_end_pos);
+  } while (++i < n
+          && sp.start == v[i]->get_separator_pos(type).start
+          && memcmp(lb.contents(), v[i]->get_label(type).contents(),
+                    sp.start) == 0);
+  return i;
+}
+
+string label_pool;
+
+label_info::label_info(const string &s)
+: count(0), total(1), length(s.length()), start(label_pool.length())
+{
+  label_pool += s;
+}
+
+static label_info **label_table = 0;
+static int label_table_size = 0;
+static int label_table_used = 0;
+
+label_info *lookup_label(const string &label)
+{
+  if (label_table == 0) {
+    label_table = new label_info *[17];
+    label_table_size = 17;
+    for (int i = 0; i < 17; i++)
+      label_table[i] = 0;
+  }
+  unsigned h = hash_string(label.contents(), label.length()) % label_table_size;
+  for (label_info **ptr = label_table + h;
+       *ptr != 0;
+       (ptr == label_table)
+       ? (ptr = label_table + label_table_size - 1)
+       : ptr--)
+    if ((*ptr)->length == label.length()
+       && memcmp(label_pool.contents() + (*ptr)->start, label.contents(),
+                 label.length()) == 0) {
+      (*ptr)->total += 1;
+      return *ptr;
+    }
+  label_info *result = *ptr = new label_info(label);
+  if (++label_table_used * 2 > label_table_size) {
+    // Rehash the table.
+    label_info **old_table = label_table;
+    int old_size = label_table_size;
+    label_table_size = next_size(label_table_size);
+    label_table = new label_info *[label_table_size];
+    int i;
+    for (i = 0; i < label_table_size; i++)
+      label_table[i] = 0;
+    for (i = 0; i < old_size; i++)
+      if (old_table[i]) {
+       unsigned h = hash_string(label_pool.contents() + old_table[i]->start,
+                                old_table[i]->length);
+       for (label_info **p = label_table + (h % label_table_size);
+            *p != 0;
+            (p == label_table)
+            ? (p = label_table + label_table_size - 1)
+            : --p)
+           ;
+       *p = old_table[i];
+       }
+    a_delete old_table;
+  }
+  return result;
+}
+
+void clear_labels()
+{
+  for (int i = 0; i < label_table_size; i++) {
+    delete label_table[i];
+    label_table[i] = 0;
+  }
+  label_table_used = 0;
+  label_pool.clear();
+}
+
+static void consider_authors(reference **start, reference **end, int i);
+
+void compute_labels(reference **v, int n)
+{
+  if (parsed_label
+      && (parsed_label->analyze() & expression::CONTAINS_AT)
+      && sort_fields.length() >= 2
+      && sort_fields[0] == 'A'
+      && sort_fields[1] == '+')
+    consider_authors(v, v + n, 0);
+  for (int i = 0; i < n; i++)
+    v[i]->compute_label();
+}
+
+
+/* A reference with a list of authors <A0,A1,...,AN> _needs_ author i
+where 0 <= i <= N if there exists a reference with a list of authors
+<B0,B1,...,BM> such that <A0,A1,...,AN> != <B0,B1,...,BM> and M >= i
+and Aj = Bj for 0 <= j < i. In this case if we can't say ``A0,
+A1,...,A(i-1) et al'' because this would match both <A0,A1,...,AN> and
+<B0,B1,...,BM>.  If a reference needs author i we only have to call
+need_author(j) for some j >= i such that the reference also needs
+author j. */
+
+/* This function handles 2 tasks:
+determine which authors are needed (cannot be elided with et al.);
+determine which authors can have only last names in the labels.
+
+References >= start and < end have the same first i author names.
+Also they're sorted by A+. */
+
+static void consider_authors(reference **start, reference **end, int i)
+{
+  if (start >= end)
+    return;
+  reference **p = start;
+  if (i >= (*p)->get_nauthors()) {
+    for (++p; p < end && i >= (*p)->get_nauthors(); p++)
+      ;
+    if (p < end && i > 0) {
+      // If we have an author list <A B C> and an author list <A B C D>,
+      // then both lists need C.
+      for (reference **q = start; q < end; q++)
+       (*q)->need_author(i - 1);
+    }
+    start = p;
+  }
+  while (p < end) {
+    reference **last_name_start = p;
+    reference **name_start = p;
+    for (++p;
+        p < end && i < (*p)->get_nauthors()
+        && same_author_last_name(**last_name_start, **p, i);
+        p++) {
+      if (!same_author_name(**name_start, **p, i)) {
+       consider_authors(name_start, p, i + 1);
+       name_start = p;
+      }
+    }
+    consider_authors(name_start, p, i + 1);
+    if (last_name_start == name_start) {
+      for (reference **q = last_name_start; q < p; q++)
+       (*q)->set_last_name_unambiguous(i);
+    }
+    // If we have an author list <A B C D> and <A B C E>, then the lists
+    // need author D and E respectively.
+    if (name_start > start || p < end) {
+      for (reference **q = last_name_start; q < p; q++)
+       (*q)->need_author(i);
+    }
+  }
+}
+
+int same_author_last_name(const reference &r1, const reference &r2, int n)
+{
+  const char *ae1;
+  const char *as1 = r1.get_sort_field(0, n, 0, &ae1);
+  assert(as1 != 0);
+  const char *ae2;
+  const char *as2 = r2.get_sort_field(0, n, 0, &ae2);
+  assert(as2 != 0);
+  return ae1 - as1 == ae2 - as2 && memcmp(as1, as2, ae1 - as1) == 0;
+}
+
+int same_author_name(const reference &r1, const reference &r2, int n)
+{
+  const char *ae1;
+  const char *as1 = r1.get_sort_field(0, n, -1, &ae1);
+  assert(as1 != 0);
+  const char *ae2;
+  const char *as2 = r2.get_sort_field(0, n, -1, &ae2);
+  assert(as2 != 0);
+  return ae1 - as1 == ae2 - as2 && memcmp(as1, as2, ae1 - as1) == 0;
+}
+
+
+void int_set::set(int i)
+{
+  assert(i >= 0);
+  int bytei = i >> 3;
+  if (bytei >= v.length()) {
+    int old_length = v.length();
+    v.set_length(bytei + 1);
+    for (int j = old_length; j <= bytei; j++)
+      v[j] = 0;
+  }
+  v[bytei] |= 1 << (i & 7);
+}
+
+int int_set::get(int i) const
+{
+  assert(i >= 0);
+  int bytei = i >> 3;
+  return bytei >= v.length() ? 0 : (v[bytei] & (1 << (i & 7))) != 0;
+}
+
+void reference::set_last_name_unambiguous(int i)
+{
+  last_name_unambiguous.set(i);
+}
+
+void reference::need_author(int n)
+{
+  if (n > last_needed_author)
+    last_needed_author = n;
+}
+
+const char *reference::get_authors(const char **end) const
+{
+  if (!computed_authors) {
+    ((reference *)this)->computed_authors = 1;
+    string &result = ((reference *)this)->authors;
+    int na = get_nauthors();
+    result.clear();
+    for (int i = 0; i < na; i++) {
+      if (last_name_unambiguous.get(i)) {
+       const char *e, *start = get_author_last_name(i, &e);
+       assert(start != 0);
+       result.append(start, e - start);
+      }
+      else {
+       const char *e, *start = get_author(i, &e);
+       assert(start != 0);
+       result.append(start, e - start);
+      }
+      if (i == last_needed_author
+         && et_al.length() > 0
+         && et_al_min_elide > 0
+         && last_needed_author + et_al_min_elide < na
+         && na >= et_al_min_total) {
+       result += et_al;
+       break;
+      }
+      if (i < na - 1) {
+       if (na == 2)
+         result += join_authors_exactly_two;
+       else if (i < na - 2)
+         result += join_authors_default;
+       else
+         result += join_authors_last_two;
+      }
+    }
+  }
+  const char *start = authors.contents();
+  *end = start + authors.length();
+  return start;
+}
+
+int reference::get_nauthors() const
+{
+  if (nauthors < 0) {
+    const char *dummy;
+    for (int na = 0; get_author(na, &dummy) != 0; na++)
+      ;
+    ((reference *)this)->nauthors = na;
+  }
+  return nauthors;
+}
diff --git a/usr/src/contrib/groff-1.08/refer/token.cc b/usr/src/contrib/groff-1.08/refer/token.cc
new file mode 100644 (file)
index 0000000..8847081
--- /dev/null
@@ -0,0 +1,370 @@
+// -*- 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 "refer.h"
+#include "token.h"
+
+#define TOKEN_TABLE_SIZE 1009
+// I believe in Icelandic thorn sorts after z.
+#define THORN_SORT_KEY "{"
+
+struct token_table_entry {
+  const char *tok;
+  token_info ti;
+  token_table_entry();
+};
+
+token_table_entry token_table[TOKEN_TABLE_SIZE];
+int ntokens = 0;
+
+static void skip_name(const char **ptr, const char *end)
+{
+  if (*ptr < end) {
+    switch (*(*ptr)++) {
+    case '(':
+      if (*ptr < end) {
+       *ptr += 1;
+       if (*ptr < end)
+         *ptr += 1;
+      }
+      break;
+    case '[':
+      while (*ptr < end)
+       if (*(*ptr)++ == ']')
+         break;
+      break;
+    }
+  }
+}
+
+int get_token(const char **ptr, const char *end)
+{
+  if (*ptr >= end)
+    return 0;
+  char c = *(*ptr)++;
+  if (c == '\\' && *ptr < end) {
+    switch (**ptr) {
+    default:
+      *ptr += 1;
+      break;
+    case '(':
+    case '[':
+      skip_name(ptr, end);
+      break;
+    case '*':
+    case 'f':
+      *ptr += 1;
+      skip_name(ptr, end);
+      break;
+    }
+  }
+  return 1;
+}
+
+token_info::token_info()
+: type(TOKEN_OTHER), sort_key(0), other_case(0)
+{
+}
+
+void token_info::set(token_type t, const char *sk, const char *oc)
+{
+  assert(oc == 0 || t == TOKEN_UPPER || t == TOKEN_LOWER);
+  type = t;
+  sort_key = sk;
+  other_case = oc;
+}
+
+void token_info::sortify(const char *start, const char *end, string &result)
+     const
+{
+  if (sort_key)
+    result += sort_key;
+  else if (type == TOKEN_UPPER || type == TOKEN_LOWER) {
+    for (; start < end; start++)
+      if (csalpha(*start))
+       result += cmlower(*start);
+  }
+}
+
+int token_info::sortify_non_empty(const char *start, const char *end) const
+{
+  if (sort_key)
+    return *sort_key != '\0';
+  if (type != TOKEN_UPPER && type != TOKEN_LOWER)
+    return 0;
+  for (; start < end; start++)
+    if (csalpha(*start))
+      return 1;
+  return 0;
+}
+
+
+void token_info::lower_case(const char *start, const char *end,
+                           string &result) const
+{
+  if (type != TOKEN_UPPER) {
+    while (start < end)
+      result += *start++;
+  }
+  else if (other_case)
+    result += other_case;
+  else {
+    while (start < end)
+      result += cmlower(*start++);
+  }
+}
+
+void token_info::upper_case(const char *start, const char *end,
+                           string &result) const
+{
+  if (type != TOKEN_LOWER) {
+    while (start < end)
+      result += *start++;
+  }
+  else if (other_case)
+    result += other_case;
+  else {
+    while (start < end)
+      result += cmupper(*start++);
+  }
+}
+
+token_table_entry::token_table_entry()
+: tok(0)
+{
+}
+
+static void store_token(const char *tok, token_type typ,
+                       const char *sk = 0, const char *oc = 0)
+{
+  unsigned n = hash_string(tok, strlen(tok)) % TOKEN_TABLE_SIZE;
+  while (n >= 0) {
+    if (token_table[n].tok == 0) {
+      if (++ntokens == TOKEN_TABLE_SIZE)
+       assert(0);
+      token_table[n].tok = tok;
+      break;
+    }
+    if (strcmp(tok, token_table[n].tok) == 0)
+      break;
+    if (--n < 0)
+      n = TOKEN_TABLE_SIZE - 1;
+  }
+  token_table[n].ti.set(typ, sk, oc);
+}
+
+
+token_info default_token_info;
+
+const token_info *lookup_token(const char *start, const char *end)
+{
+  unsigned n = hash_string(start, end - start) % TOKEN_TABLE_SIZE;
+  while (n >= 0) {
+    if (token_table[n].tok == 0)
+      break;
+    if (strlen(token_table[n].tok) == end - start
+       && memcmp(token_table[n].tok, start, end - start) == 0)
+      return &(token_table[n].ti);
+    if (--n < 0)
+      n = TOKEN_TABLE_SIZE - 1;
+  }
+  return &default_token_info;
+}
+
+static void init_ascii()
+{
+  for (const char *p = "abcdefghijklmnopqrstuvwxyz"; *p; p++) {
+    char buf[2];
+    buf[0] = *p;
+    buf[1] = '\0';
+    store_token(strsave(buf), TOKEN_LOWER);
+    buf[0] = cmupper(buf[0]);
+    store_token(strsave(buf), TOKEN_UPPER);
+  }
+  for (p = "0123456789"; *p; p++) {
+    char buf[2];
+    buf[0] = *p;
+    buf[1] = '\0';
+    const char *s = strsave(buf);
+    store_token(s, TOKEN_OTHER, s);
+  }
+  for (p = ".,:;?!"; *p; p++) {
+    char buf[2];
+    buf[0] = *p;
+    buf[1] = '\0';
+    store_token(strsave(buf), TOKEN_PUNCT);
+  }
+  store_token("-", TOKEN_HYPHEN);
+}
+
+static void store_letter(const char *lower, const char *upper,
+                 const char *sort_key = 0)
+{
+  store_token(lower, TOKEN_LOWER, sort_key, upper);
+  store_token(upper, TOKEN_UPPER, sort_key, lower);
+}
+
+static void init_letter(unsigned char uc_code, unsigned char lc_code,
+                const char *sort_key)
+{
+  char lbuf[2];
+  lbuf[0] = lc_code;
+  lbuf[1] = 0;
+  char ubuf[2];
+  ubuf[0] = uc_code;
+  ubuf[1] = 0;
+  store_letter(strsave(lbuf), strsave(ubuf), sort_key);
+}
+
+static void init_latin1()
+{
+  init_letter(0xc0, 0xe0, "a");
+  init_letter(0xc1, 0xe1, "a");
+  init_letter(0xc2, 0xe2, "a");
+  init_letter(0xc3, 0xe3, "a");
+  init_letter(0xc4, 0xe4, "a");
+  init_letter(0xc5, 0xe5, "a");
+  init_letter(0xc6, 0xe6, "ae");
+  init_letter(0xc7, 0xe7, "c");
+  init_letter(0xc8, 0xe8, "e");
+  init_letter(0xc9, 0xe9, "e");
+  init_letter(0xca, 0xea, "e");
+  init_letter(0xcb, 0xeb, "e");
+  init_letter(0xcc, 0xec, "i");
+  init_letter(0xcd, 0xed, "i");
+  init_letter(0xce, 0xee, "i");
+  init_letter(0xcf, 0xef, "i");
+
+  init_letter(0xd0, 0xf0, "d");
+  init_letter(0xd1, 0xf1, "n");
+  init_letter(0xd2, 0xf2, "o");
+  init_letter(0xd3, 0xf3, "o");
+  init_letter(0xd4, 0xf4, "o");
+  init_letter(0xd5, 0xf5, "o");
+  init_letter(0xd6, 0xf6, "o");
+  init_letter(0xd8, 0xf8, "o");
+  init_letter(0xd9, 0xf9, "u");
+  init_letter(0xda, 0xfa, "u");
+  init_letter(0xdb, 0xfb, "u");
+  init_letter(0xdc, 0xfc, "u");
+  init_letter(0xdd, 0xfd, "y");
+  init_letter(0xde, 0xfe, THORN_SORT_KEY);
+
+  store_token("\337", TOKEN_LOWER, "ss", "SS");
+  store_token("\377", TOKEN_LOWER, "y", "Y");
+}
+
+static void init_two_char_letter(char l1, char l2, char u1, char u2,
+                                const char *sk = 0)
+{
+  char buf[6];
+  buf[0] = '\\';
+  buf[1] = '(';
+  buf[2] = l1;
+  buf[3] = l2;
+  buf[4] = '\0';
+  const char *p = strsave(buf);
+  buf[2] = u1;
+  buf[3] = u2;
+  store_letter(p, strsave(buf), sk);
+  buf[1] = '[';
+  buf[4] = ']';
+  buf[5] = '\0';
+  p = strsave(buf);
+  buf[2] = l1;
+  buf[3] = l2;
+  store_letter(strsave(buf), p, sk);
+  
+}
+
+static void init_special_chars()
+{
+  for (const char *p = "':^`~"; *p; p++)
+    for (const char *q = "aeiouy"; *q; q++) {
+      // Use a variable to work around bug in gcc 2.0
+      char c = cmupper(*q);
+      init_two_char_letter(*p, *q, *p, c);
+    }
+  for (p = "/l/o~n,coeaeij"; *p; p += 2) {
+    // Use variables to work around bug in gcc 2.0
+    char c0 = cmupper(p[0]);
+    char c1 = cmupper(p[1]);
+    init_two_char_letter(p[0], p[1], c0, c1);
+  }
+  init_two_char_letter('v', 's', 'v', 'S', "s");
+  init_two_char_letter('v', 'z', 'v', 'Z', "z");
+  init_two_char_letter('o', 'a', 'o', 'A', "a");
+  init_two_char_letter('T', 'p', 'T', 'P', THORN_SORT_KEY);
+  init_two_char_letter('-', 'd', '-', 'D');
+  
+  store_token("\\(ss", TOKEN_LOWER, 0, "SS");
+  store_token("\\[ss]", TOKEN_LOWER, 0, "SS");
+
+  store_token("\\(Sd", TOKEN_LOWER, "d", "\\(-D");
+  store_token("\\[Sd]", TOKEN_LOWER, "d", "\\[-D]");
+  store_token("\\(hy", TOKEN_HYPHEN);
+  store_token("\\[hy]", TOKEN_HYPHEN);
+}
+
+static void init_strings()
+{
+  char buf[6];
+  buf[0] = '\\';
+  buf[1] = '*';
+  for (const char *p = "'`^^,:~v_o./;"; *p; p++) {
+    buf[2] = *p;
+    buf[3] = '\0';
+    store_token(strsave(buf), TOKEN_ACCENT);
+    buf[2] = '[';
+    buf[3] = *p;
+    buf[4] = ']';
+    buf[5] = '\0';
+    store_token(strsave(buf), TOKEN_ACCENT);
+  }
+
+  // -ms special letters
+  store_letter("\\*(th", "\\*(Th", THORN_SORT_KEY);
+  store_letter("\\*[th]", "\\*[Th]", THORN_SORT_KEY);
+  store_letter("\\*(d-", "\\*(D-");
+  store_letter("\\*[d-]", "\\*[D-]");
+  store_letter("\\*(ae", "\\*(Ae", "ae");
+  store_letter("\\*[ae]", "\\*[Ae]", "ae");
+  store_letter("\\*(oe", "\\*(Oe", "oe");
+  store_letter("\\*[oe]", "\\*[Oe]", "oe");
+
+  store_token("\\*3", TOKEN_LOWER, "y", "Y");
+  store_token("\\*8", TOKEN_LOWER, "ss", "SS");
+  store_token("\\*q", TOKEN_LOWER, "o", "O");
+}
+
+struct token_initer {
+  token_initer();
+};
+
+static token_initer the_token_initer;
+
+token_initer::token_initer()
+{
+  init_ascii();
+  init_latin1();
+  init_special_chars();
+  init_strings();
+  default_token_info.set(TOKEN_OTHER);
+}
diff --git a/usr/src/contrib/groff-1.08/refer/token.h b/usr/src/contrib/groff-1.08/refer/token.h
new file mode 100644 (file)
index 0000000..c6445d2
--- /dev/null
@@ -0,0 +1,81 @@
+// -*- 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. */
+
+enum token_type {
+  TOKEN_OTHER,
+  TOKEN_UPPER,
+  TOKEN_LOWER,
+  TOKEN_ACCENT,
+  TOKEN_PUNCT,
+  TOKEN_HYPHEN
+};
+
+class token_info {
+private:
+  token_type type;
+  const char *sort_key;
+  const char *other_case;
+public:
+  token_info();
+  void set(token_type, const char *sk = 0, const char *oc = 0);
+  void lower_case(const char *start, const char *end, string &result) const;
+  void upper_case(const char *start, const char *end, string &result) const;
+  void sortify(const char *start, const char *end, string &result) const;
+  int sortify_non_empty(const char *start, const char *end) const;
+  int is_upper() const;
+  int is_lower() const;
+  int is_accent() const;
+  int is_other() const;
+  int is_punct() const;
+  int is_hyphen() const;
+};
+
+inline int token_info::is_upper() const
+{
+  return type == TOKEN_UPPER;
+}
+
+inline int token_info::is_lower() const
+{
+  return type == TOKEN_LOWER;
+}
+
+inline int token_info::is_accent() const
+{
+  return type == TOKEN_ACCENT;
+}
+
+inline int token_info::is_other() const
+{
+  return type == TOKEN_OTHER;
+}
+
+inline int token_info::is_punct() const
+{
+  return type == TOKEN_PUNCT;
+}
+
+inline int token_info::is_hyphen() const
+{
+  return type == TOKEN_HYPHEN;
+}
+
+int get_token(const char **ptr, const char *end);
+const token_info *lookup_token(const char *start, const char *end);
diff --git a/usr/src/contrib/groff-1.08/tfmtodit/tfmtodit.cc b/usr/src/contrib/groff-1.08/tfmtodit/tfmtodit.cc
new file mode 100644 (file)
index 0000000..851735d
--- /dev/null
@@ -0,0 +1,850 @@
+// -*- 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. */
+
+/* I have tried to incorporate the changes needed for TeX 3.0 tfm files,
+but I haven't tested them. */
+
+/* Groff requires more font metric information than TeX.  The reason
+for this is that TeX has separate Math Italic fonts, whereas groff
+uses normal italic fonts for math.  The two additional pieces of
+information required by groff correspond to the two arguments to the
+math_fit() macro in the Metafont programs for the CM fonts. In the
+case of a font for which math_fitting is false, these two arguments
+are normally ignored by Metafont. We need to get hold of these two
+parameters and put them in the groff font file.
+
+We do this by loading this definition after cmbase when creating cm.base.
+
+def ignore_math_fit(expr left_adjustment,right_adjustment) =
+ special "adjustment";
+ numspecial left_adjustment*16/designsize;
+ numspecial right_adjustment*16/designsize;
+ enddef;
+
+This puts the two arguments to the math_fit macro into the gf file.
+(They will appear in the gf file immediately before the character to
+which they apply.)  We then create a gf file using this cm.base.  Then
+we run tfmtodit and specify this gf file with the -g option.
+
+This need only be done for a font for which math_fitting is false;
+When it's true, the left_correction and subscript_correction should
+both be zero. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <errno.h>
+#include "lib.h"
+#include "errarg.h"
+#include "error.h"
+#include "assert.h"
+#include "cset.h"
+
+/* Values in the tfm file should be multiplied by this. */
+
+#define MULTIPLIER 1
+
+struct char_info_word {
+  unsigned char width_index;
+  char height_index;
+  char depth_index;
+  char italic_index;
+  char tag;
+  unsigned char remainder;
+};
+
+struct lig_kern_command {
+  unsigned char skip_byte;
+  unsigned char next_char;
+  unsigned char op_byte;
+  unsigned char remainder;
+};
+
+class tfm {
+  int bc;
+  int ec;
+  int nw;
+  int nh;
+  int nd;
+  int ni;
+  int nl;
+  int nk;
+  int np;
+  int cs;
+  int ds;
+  char_info_word *char_info;
+  int *width;
+  int *height;
+  int *depth;
+  int *italic;
+  lig_kern_command *lig_kern;
+  int *kern;
+  int *param;
+public:
+  tfm();
+  ~tfm();
+  int load(const char *);
+  int contains(int);
+  int get_width(int);
+  int get_height(int);
+  int get_depth(int);
+  int get_italic(int);
+  int get_param(int, int *);
+  int get_checksum();
+  int get_design_size();
+  int get_lig(unsigned char, unsigned char, unsigned char *);
+  friend class kern_iterator;
+};
+
+class kern_iterator {
+  tfm *t;
+  int c;
+  int i;
+public:
+  kern_iterator(tfm *);
+  int next(unsigned char *c1, unsigned char *c2, int *k);
+};
+
+
+kern_iterator::kern_iterator(tfm *p)
+: t(p), i(-1), c(t->bc)
+{
+}
+
+int kern_iterator::next(unsigned char *c1, unsigned char *c2, int *k)
+{
+  for (; c <= t->ec; c++)
+    if (t->char_info[c - t->bc].tag == 1) {
+      if (i < 0) {
+       i = t->char_info[c - t->bc].remainder;
+       if (t->lig_kern[i].skip_byte > 128)
+         i = (256*t->lig_kern[i].op_byte
+                  + t->lig_kern[i].remainder);
+      }
+      for (;;) {
+       int skip = t->lig_kern[i].skip_byte;
+       if (skip <= 128 && t->lig_kern[i].op_byte >= 128) {
+         *c1 = c;
+         *c2 = t->lig_kern[i].next_char;
+         *k = t->kern[256*(t->lig_kern[i].op_byte - 128)
+                      + t->lig_kern[i].remainder];
+         if (skip == 128) {
+           c++;
+           i = -1;
+         }
+         else
+           i += skip + 1;
+         return 1;
+       }
+       if (skip >= 128)
+         break;
+       i += skip + 1;
+      }
+      i = -1;
+    }
+  return 0;
+}
+         
+tfm::tfm()
+: char_info(0), width(0), height(0), depth(0), italic(0), lig_kern(0),
+  kern(0), param(0)
+{
+}
+
+int tfm::get_lig(unsigned char c1, unsigned char c2, unsigned char *cp)
+{
+  if (contains(c1) && char_info[c1 - bc].tag == 1) {
+    int i = char_info[c1 - bc].remainder;
+    if (lig_kern[i].skip_byte > 128)
+      i = 256*lig_kern[i].op_byte + lig_kern[i].remainder;
+    for (;;) {
+      int skip = lig_kern[i].skip_byte;
+      if (skip > 128)
+       break;
+      // We are only interested in normal ligatures, for which
+      // op_byte == 0.
+      if (lig_kern[i].op_byte == 0
+         && lig_kern[i].next_char == c2) {
+       *cp = lig_kern[i].remainder;
+       return 1;
+      }
+      if (skip == 128)
+       break;
+      i += skip + 1;
+    }
+  }
+  return 0;
+}
+
+int tfm::contains(int i)
+{
+  return i >= bc && i <= ec && char_info[i - bc].width_index != 0;
+}
+
+int tfm::get_width(int i)
+{
+  return width[char_info[i - bc].width_index];
+}
+
+int tfm::get_height(int i)
+{
+  return height[char_info[i - bc].height_index];
+}
+
+int tfm::get_depth(int i)
+{
+  return depth[char_info[i - bc].depth_index];
+}
+
+int tfm::get_italic(int i)
+{
+  return italic[char_info[i - bc].italic_index];
+}
+
+int tfm::get_param(int i, int *p)
+{
+  if (i <= 0 || i > np)
+    return 0;
+  else {
+    *p = param[i - 1];
+    return 1;
+  }
+}
+
+int tfm::get_checksum()
+{
+  return cs;
+}
+
+int tfm::get_design_size()
+{
+  return ds;
+}
+
+tfm::~tfm()
+{
+  a_delete char_info;
+  a_delete width;
+  a_delete height;
+  a_delete depth;
+  a_delete italic;
+  a_delete lig_kern;
+  a_delete kern;
+  a_delete param;
+}
+  
+int read2(unsigned char *&s)
+{
+  int n;
+  n = *s++ << 8;
+  n |= *s++;
+  return n;
+}
+
+int read4(unsigned char *&s)
+{
+  int n;
+  n = *s++ << 24;
+  n |= *s++ << 16;
+  n |= *s++ << 8;
+  n |= *s++;
+  return n;
+}
+
+
+int tfm::load(const char *file)
+{
+  errno = 0;
+  FILE *fp = fopen(file, "r");
+  if (!fp) {
+    error("can't open `%1': %2", file, strerror(errno));
+    return 0;
+  }
+  int c1 = getc(fp);
+  int c2 = getc(fp);
+  if (c1 == EOF || c2 == EOF) {
+    fclose(fp);
+    error("unexpected end of file on `%1'", file);
+    return 0;
+  }
+  int lf = (c1 << 8) + c2;
+  int toread = lf*4 - 2;
+  unsigned char *buf = new unsigned char[toread];
+  if (fread(buf, 1, toread, fp) != toread) {
+    if (feof(fp))
+      error("unexpected end of file on `%1'", file);
+    else
+      error("error on file `%1'", file);
+    a_delete buf;
+    fclose(fp);
+    return 0;
+  }
+  fclose(fp);
+  if (lf < 6) {
+    error("bad tfm file `%1': impossibly short", file);
+    a_delete buf;
+    return 0;
+  }
+  unsigned char *ptr = buf;
+  int lh = read2(ptr);
+  bc = read2(ptr);
+  ec = read2(ptr);
+  nw = read2(ptr);
+  nh = read2(ptr);
+  nd = read2(ptr);
+  ni = read2(ptr);
+  nl = read2(ptr);
+  nk = read2(ptr);
+  int ne = read2(ptr);
+  np = read2(ptr);
+  if (6 + lh + (ec - bc + 1) + nw + nh + nd + ni + nl + nk + ne + np != lf) {
+    error("bad tfm file `%1': lengths do not sum", file);
+    a_delete buf;
+    return 0;
+  }
+  if (lh < 2) {
+    error("bad tfm file `%1': header too short", file);
+    a_delete buf;
+    return 0;
+  }
+  char_info = new char_info_word[ec - bc + 1];
+  width = new int[nw];
+  height = new int[nh];
+  depth = new int[nd];
+  italic = new int[ni];
+  lig_kern = new lig_kern_command[nl];
+  kern = new int[nk];
+  param = new int[np];
+  int i;
+  cs = read4(ptr);
+  ds = read4(ptr);
+  ptr += (lh-2)*4;
+  for (i = 0; i < ec - bc + 1; i++) {
+    char_info[i].width_index = *ptr++;
+    unsigned char tem = *ptr++;
+    char_info[i].depth_index = tem & 0xf;
+    char_info[i].height_index = tem >> 4;
+    tem = *ptr++;
+    char_info[i].italic_index = tem >> 2;
+    char_info[i].tag = tem & 3;
+    char_info[i].remainder = *ptr++;
+  }
+  for (i = 0; i < nw; i++)
+    width[i] = read4(ptr);
+  for (i = 0; i < nh; i++)
+    height[i] = read4(ptr);
+  for (i = 0; i < nd; i++)
+    depth[i] = read4(ptr);
+  for (i = 0; i < ni; i++)
+    italic[i] = read4(ptr);
+  for (i = 0; i < nl; i++) {
+    lig_kern[i].skip_byte = *ptr++;
+    lig_kern[i].next_char = *ptr++;
+    lig_kern[i].op_byte = *ptr++;
+    lig_kern[i].remainder = *ptr++;
+  }
+  for (i = 0; i < nk; i++)
+    kern[i] = read4(ptr);
+  ptr += ne*4;
+  for (i = 0; i < np; i++)
+    param[i] = read4(ptr);
+  assert(ptr == buf + lf*4 - 2);
+  a_delete buf;
+  return 1;
+}
+
+class gf {
+  int left[256];
+  int right[256];
+  static int sread4(int *p, FILE *fp);
+  static int uread3(int *p, FILE *fp);
+  static int uread2(int *p, FILE *fp);
+  static int skip(int n, FILE *fp);
+public:
+  gf();
+  int load(const char *file);
+  int get_left_adjustment(int i) { return left[i]; }
+  int get_right_adjustment(int i) { return right[i]; }
+};
+
+gf::gf()
+{
+  for (int i = 0; i < 256; i++)
+    left[i] = right[i] = 0;
+}
+
+int gf::load(const char *file)
+{
+  enum {
+    paint_0 = 0,
+    paint1 = 64,
+    boc = 67,
+    boc1 = 68,
+    eoc = 69,
+    skip0 = 70,
+    skip1 = 71,
+    new_row_0 = 74,
+    xxx1 = 239,
+    yyy = 243,
+    no_op = 244,
+    pre = 247,
+    post = 248
+  };
+  int got_an_adjustment = 0;
+  int pending_adjustment = 0;
+  int left_adj, right_adj;
+  const int gf_id_byte = 131;
+  errno = 0;
+  FILE *fp = fopen(file, "r");
+  if (!fp) {
+    error("can't open `%1': %2", file, strerror(errno));
+    return 0;
+  }
+  if (getc(fp) != pre || getc(fp) != gf_id_byte) {
+    error("bad gf file");
+    return 0;
+  }
+  int n = getc(fp);
+  if (n == EOF)
+    goto eof;
+  if (!skip(n, fp))
+    goto eof;
+  for (;;) {
+    int op = getc(fp);
+    if (op == EOF)
+      goto eof;
+    if (op == post)
+      break;
+    if ((op >= paint_0 && op <= paint_0 + 63)
+       || (op >= new_row_0 && op <= new_row_0 + 164))
+      continue;
+    switch (op) {
+    case no_op:
+    case eoc:
+    case skip0:
+      break;
+    case paint1:
+    case skip1:
+      if (!skip(1, fp))
+       goto eof;
+      break;
+    case paint1 + 1:
+    case skip1 + 1:
+      if (!skip(2, fp))
+       goto eof;
+      break;
+    case paint1 + 2:
+    case skip1 + 2:
+      if (!skip(3, fp))
+       goto eof;
+      break;
+    case boc:
+      {
+       int code;
+       if (!sread4(&code, fp))
+         goto eof;
+       if (pending_adjustment) {
+         pending_adjustment = 0;
+         left[code & 0377] = left_adj;
+         right[code & 0377] = right_adj;
+       }
+       if (!skip(20, fp))
+         goto eof;
+       break;
+      }
+    case boc1:
+      {
+       int code = getc(fp);
+       if (code == EOF)
+         goto eof;
+       if (pending_adjustment) {
+         pending_adjustment = 0;
+         left[code] = left_adj;
+         right[code] = right_adj;
+       }
+       if (!skip(4, fp))
+         goto eof;
+       break;
+      }
+    case xxx1:
+      {
+       int len = getc(fp);
+       if (len == EOF)
+         goto eof;
+       char buf[256];
+       if (fread(buf, 1, len, fp) != len)
+         goto eof;
+       if (len == 10 /* strlen("adjustment") */
+           && memcmp(buf, "adjustment", len) == 0) {
+         int c = getc(fp);
+         if (c != yyy) {
+           if (c != EOF)
+             ungetc(c, fp);
+           break;
+         }
+         if (!sread4(&left_adj, fp))
+           goto eof;
+         c = getc(fp);
+         if (c != yyy) {
+           if (c != EOF)
+             ungetc(c, fp);
+           break;
+         }
+         if (!sread4(&right_adj, fp))
+           goto eof;
+         got_an_adjustment = 1;
+         pending_adjustment = 1;
+       }
+       break;
+      }
+    case xxx1 + 1:
+      if (!uread2(&n, fp) || !skip(n, fp))
+       goto eof;
+      break;
+    case xxx1 + 2:
+      if (!uread3(&n, fp) || !skip(n, fp))
+       goto eof;
+      break;
+    case xxx1 + 3:
+      if (!sread4(&n, fp) || !skip(n, fp))
+       goto eof;
+      break;
+    case yyy:
+      if (!skip(4, fp))
+       goto eof;
+      break;
+    default:
+      fatal("unrecognized opcode `%1'", op);
+      break;
+    }
+  }
+  if (!got_an_adjustment)
+    warning("no adjustment specials found in gf file");
+  return 1;
+ eof:
+  error("unexpected end of file");
+  return 0;
+}
+
+int gf::sread4(int *p, FILE *fp)
+{
+  *p = getc(fp);
+  if (*p >= 128)
+    *p -= 256;
+  *p <<= 8;
+  *p |= getc(fp);
+  *p <<= 8;
+  *p |= getc(fp);
+  *p <<= 8;
+  *p |= getc(fp);
+  return !ferror(fp) && !feof(fp);
+}
+
+int gf::uread3(int *p, FILE *fp)
+{
+  *p = getc(fp);
+  *p <<= 8;
+  *p |= getc(fp);
+  *p <<= 8;
+  *p |= getc(fp);
+  return !ferror(fp) && !feof(fp);
+}
+
+int gf::uread2(int *p, FILE *fp)
+{
+  *p = getc(fp);
+  *p <<= 8;
+  *p |= getc(fp);
+  return !ferror(fp) && !feof(fp);
+}
+
+int gf::skip(int n, FILE *fp)
+{
+  while (--n >= 0)
+    if (getc(fp) == EOF)
+      return 0;
+  return 1;
+}
+
+
+struct char_list {
+  char *ch;
+  char_list *next;
+  char_list(const char *, char_list * = 0);
+};
+
+char_list::char_list(const char *s, char_list *p) : ch(strsave(s)), next(p)
+{
+}
+
+
+int read_map(const char *file, char_list **table)
+{
+  errno = 0;
+  FILE *fp = fopen(file, "r");
+  if (!fp) {
+    error("can't open `%1': %2", file, strerror(errno));
+    return 0;
+  }
+  for (int i = 0; i < 256; i++)
+    table[i] = 0;
+  char buf[512];
+  int lineno = 0;
+  while (fgets(buf, int(sizeof(buf)), fp)) {
+    lineno++;
+    char *ptr = buf;
+    while (csspace(*ptr))
+      ptr++;
+    if (*ptr == '\0' || *ptr == '#')
+      continue;
+    ptr = strtok(ptr, " \n\t");
+    if (!ptr)
+      continue;
+    int n;
+    if (sscanf(ptr, "%d", &n) != 1) {
+      error("%1:%2: bad map file", file, lineno);
+      fclose(fp);
+      return 0;
+    }
+    if (n < 0 || n > 255) {
+      error("%1:%2: code out of range", file, lineno);
+      fclose(fp);
+      return 0;
+    }
+    ptr = strtok(0, " \n\t");
+    if (!ptr) {
+      error("%1:%2: missing names", file, lineno);
+      fclose(fp);
+      return 0;
+    }
+    for (; ptr; ptr = strtok(0, " \n\t"))
+      table[n] = new char_list(ptr, table[n]);
+  }
+  fclose(fp);
+  return 1;
+}
+
+
+/* Every character that can participate in a ligature appears in the
+lig_chars table. `ch' gives the full-name of the character, `name'
+gives the groff name of the character, `i' gives its index in
+the encoding, which is filled in later  (-1 if it does not appear). */
+
+struct {
+  const char *ch;
+  int i;
+} lig_chars[] = {
+  "f", -1,
+  "i", -1,
+  "l", -1,
+  "ff", -1,
+  "fi", -1,
+  "fl", -1,
+  "Fi", -1,
+  "Fl", -1,
+};
+
+// Indices into lig_chars[].
+
+enum { CH_f, CH_i, CH_l, CH_ff, CH_fi, CH_fl, CH_ffi, CH_ffl };
+
+// Each possible ligature appears in this table.
+
+struct {
+  unsigned char c1, c2, res;
+  const char *ch;
+} lig_table[] = {
+  CH_f, CH_f, CH_ff, "ff",
+  CH_f, CH_i, CH_fi, "fi",
+  CH_f, CH_l, CH_fl, "fl",
+  CH_ff, CH_i, CH_ffi, "ffi",
+  CH_ff, CH_l, CH_ffl, "ffl",
+  };
+
+static void usage();
+  
+int main(int argc, char **argv)
+{
+  program_name = argv[0];
+  int special_flag = 0;
+  int skewchar = -1;
+  int opt;
+  const char *gf_file = 0;
+  while ((opt = getopt(argc, argv, "svg:k:")) != EOF)
+    switch (opt) {
+    case 'g':
+      gf_file = optarg;
+      break;
+    case 's':
+      special_flag = 1;
+      break;
+    case 'k':
+      {
+       char *ptr;
+       long n = strtol(optarg, &ptr, 0);
+       if ((n == 0 && ptr == optarg)
+           || *ptr != '\0'
+           || n < 0
+           || n > UCHAR_MAX)
+         error("invalid skewchar");
+       else
+         skewchar = (int)n;
+       break;
+      }
+    case 'v':
+      {
+       extern const char *version_string;
+       fprintf(stderr, "tfmtodit version %s\n", version_string);
+       fflush(stderr);
+       break;
+      }
+    case '?':
+      usage();
+      break;
+    case EOF:
+      assert(0);
+    }
+  if (argc - optind != 3)
+    usage();
+  gf g;
+  if (gf_file) {
+    if (!g.load(gf_file))
+      return 1;
+  }
+  const char *tfm_file = argv[optind];
+  const char *map_file = argv[optind + 1];
+  const char *font_file = argv[optind + 2];
+  tfm t;
+  if (!t.load(tfm_file))
+    return 1;
+  char_list *table[256];
+  if (!read_map(map_file, table))
+    return 1;
+  errno = 0;
+  if (!freopen(font_file, "w", stdout)) {
+    error("can't open `%1' for writing: %2", font_file, strerror(errno));
+    return 1;
+  }
+  printf("name %s\n", font_file);
+  if (special_flag)
+    fputs("special\n", stdout);
+  char *internal_name = strsave(argv[optind]);
+  int len = strlen(internal_name);
+  if (len > 4 && strcmp(internal_name + len - 4, ".tfm") == 0)
+    internal_name[len - 4] = '\0';
+  char *s = strrchr(internal_name, '/');
+  printf("internalname %s\n", s ? s + 1 : internal_name);
+  int n;
+  if (t.get_param(2, &n)) {
+    if (n > 0)
+      printf("spacewidth %d\n", n*MULTIPLIER);
+  }
+  if (t.get_param(1, &n) && n != 0)
+    printf("slant %f\n", atan2(n/double(1<<20), 1.0)*180.0/M_PI);
+  int xheight;
+  if (!t.get_param(5, &xheight))
+    xheight = 0;
+  int i;
+  // Print the list of ligatures.
+  // First find the indices of each character that can participate in
+  // a ligature.
+  for (i = 0; i < 256; i++)
+    for (int j = 0; j < sizeof(lig_chars)/sizeof(lig_chars[0]); j++)
+      for (char_list *p = table[i]; p; p = p->next)
+       if (strcmp(lig_chars[j].ch, p->ch) == 0)
+         lig_chars[j].i = i;
+  // For each possible ligature, if its participants all exist,
+  // and it appears as a ligature in the tfm file, include in
+  // the list of ligatures.
+  int started = 0;
+  for (i = 0; i < sizeof(lig_table)/sizeof(lig_table[0]); i++) {
+    int i1 = lig_chars[lig_table[i].c1].i;
+    int i2 = lig_chars[lig_table[i].c2].i;
+    int r = lig_chars[lig_table[i].res].i;
+    if (i1 >= 0 && i2 >= 0 && r >= 0) {
+      unsigned char c;
+      if (t.get_lig(i1, i2, &c) && c == r) {
+       if (!started) {
+         started = 1;
+         fputs("ligatures", stdout);
+       }
+       printf(" %s", lig_table[i].ch);
+      }
+    }
+  }
+  if (started)
+    fputs(" 0\n", stdout);
+  printf("checksum %d\n", t.get_checksum());
+  printf("designsize %d\n", t.get_design_size());
+  // Now print out the kerning information.
+  int had_kern = 0;
+  kern_iterator iter(&t);
+  unsigned char c1, c2;
+  int k;
+  while (iter.next(&c1, &c2, &k))
+    if (c2 != skewchar) {
+      k *= MULTIPLIER;
+      char_list *q = table[c2];
+      for (char_list *p1 = table[c1]; p1; p1 = p1->next)
+       for (char_list *p2 = q; p2; p2 = p2->next) {
+         if (!had_kern) {
+           printf("kernpairs\n");
+           had_kern = 1;
+         }
+         printf("%s %s %d\n", p1->ch, p2->ch, k);
+       }
+    }
+  printf("charset\n");
+  char_list unnamed("---");
+  for (i = 0; i < 256; i++) 
+    if (t.contains(i)) {
+      char_list *p = table[i] ? table[i] : &unnamed;
+      int m[6];
+      m[0] = t.get_width(i);
+      m[1] = t.get_height(i);
+      m[2] = t.get_depth(i);
+      m[3] = t.get_italic(i);
+      m[4] = g.get_left_adjustment(i);
+      m[5] = g.get_right_adjustment(i);
+      printf("%s\t%d", p->ch, m[0]*MULTIPLIER);
+      for (int j = int(sizeof(m)/sizeof(m[0])) - 1; j > 0; j--)
+       if (m[j] != 0)
+         break;
+      for (int k = 1; k <= j; k++)
+       printf(",%d", m[k]*MULTIPLIER);
+      int type = 0;
+      if (m[2] > 0)
+       type = 1;
+      if (m[1] > xheight)
+       type += 2;
+      printf("\t%d\t%04o\n", type, i);
+      for (p = p->next; p; p = p->next)
+       printf("%s\t\"\n", p->ch);
+    }
+  return 0;
+}
+
+static void usage()
+{
+  fprintf(stderr, "usage: %s [-sv] [-g gf_file] [-k skewchar] tfm_file map_file font\n",
+         program_name);
+  exit(1);
+}