6.3. qweave.cc


6.3.1

Produce LATEX or HTML documentation from a QefDoc file.

TODO: allow multiple files to be easily turned into a single .tex file.

TODO: use popt for the option handling.

TODO: allow the filename which will be typeset to be specified with a command line arg. The filename for error messages and the 2nd line of TEX comments should still be the real one, but it might be useful to change the one that appears in the final output so that the file's position in the source tree can be shown even if `qweave' is being run on it from the directory its in.

The following error codes can be returned:

#include <string>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <cassert>

#define QWEAVE_NO_EXTERNS
#include "qweave.hh"

const char *style_sheet_name = "qprettyl.css";

6.3.2
const char *language_names[] =
{
   "Plain text",
   "C++",
   "Perl",
   "Lex/Flex",
   "Python",
   "Sed"
};

6.3.3

Prototypes for the interfaces to the source code pretty printers.

const string &pretty_print_cxx (const string &source);
//const string &pretty_print_lex (const string &source);
const string &pretty_print_python (const string &source);
const string &pretty_print_text (const string &source);

6.3.4

Prototypes for functions defined below.

void parse_index (const char *filename);
void parse (QefDocFile &file);
void parse_error (const char *msg, int code = 3);  // TODO: noreturn thing.
bool read_line (FILE *ins, string &line);
void inline_command (const string &com, const string &arg, string &s);
bool arg_error (const string &com, bool args_wanted);
void new_chunk (QefDocFile &file, const string &doc, const string &src);
void output_header (FILE *ous, const string &title);
void output_toc ();
bool output_file (QefDocFile &file, int refn, int chnsec, int chnfile);
void output_footer (FILE *ous);
const string &pretty_print (Language lang, const string &src);
FILE *open_input_file (const char *filename);
void close_files ();
void trim_end (string &s);

6.3.5

Pointers to the input and output files, respectively. If filenames are not specified on the command line then ins will point to `stdin' and ous to `stdout'.

FILE *ous = 0;

6.3.6

This is true if the output should be in HTML. It is set to `true' if the option `--html' is given.

bool use_html = false;

6.3.7

Set to `true' if the option `--no-header' is given. When set the starting and finishing code will not be output, only the code for the actual chunks. This allows the resulting code to be included into a larger document.

bool option_no_header = false;

6.3.8

This will be `true' if `--index' is specified, in which case the input file is used as the index (.qex) file which lists the actual QefDoc source files to be used.

bool option_index = false;

6.3.9

This option only applies when `--html' is also used. It causes the documentation for each file to be output to a separate HTML file.

bool option_split = false;

6.3.10

If specified this will suppress the table of contents, which is generated by default if an index file is used. TODO

bool option_no_contents = false;

6.3.11

Used in the parser to keep track of the current line number and file name of the input file. This is useful for printing error messages in `parse_error'.

int line_num = 1;
const char *current_filename = 0;

6.3.12
string main_title;

6.3.13

The list of QefDoc source files to read. If `--index' is given on the command line then these will be read from the input file, otherwise the input file itself is the only one.

list <QefDocFile *> files;
typedef list <QefDocFile *>::iterator QefDocFileIt;

6.3.14
int
main (int argc, char **argv)
{
   const char *inpfilename = 0;

6.3.15

Decode any command line options.

   int argi = 1;
   while (argv[argi][0] == '-')
   {
      if (strcmp (argv[argi], "--html") == 0)
         use_html = true;
      else if (strcmp (argv[argi], "--no-header") == 0)
         option_no_header = true;
      else if (strcmp (argv[argi], "--index") == 0)
         option_index = true;
      else if (strcmp (argv[argi], "--split") == 0)
         option_split = true;
      else
      {
         fprintf (stderr, "%s: unrecognized option `%s'.\n",
                  argv[0], argv[argi]);
         exit (1);
      }
      ++argi;
   }

6.3.16

Try to open the input file, or use `stdin' if none was specified.

   if (argc - argi ≥ 1)
      inpfilename = argv[argi++];

6.3.17

Try to open the output file, or use `stdout' if one wasn't specified.

   if (argc - argi ≥ 1)
   {
      ous = fopen (argv[argi++], "w");
      if (!ous)
      {
         fprintf (stderr, "Error opening output file `%s'.\n", argv[argi - 1]);
         return 2;
      }
   }
   else
      ous = stdout;

6.3.18
   if (option_index)
      parse_index (inpfilename);
   else
      files.push_back (new QefDocFile (inpfilename, "", 0, 0));

6.3.19

Parse the QefDoc files and store the documentation and source strings in the files objects.

   for (QefDocFileIt i = files.begin(); i ≠ files.end(); ++i)
      parse (**i);

6.3.20

Output the TEX or HTML source, first for the table of contents and then for each file in turn.

XXX: obviously the title shouldn't be hardcoded in.

   output_header (ous, "Final Year Project Code");

   if (files.size() > 1 && !option_no_contents)
      output_toc();

   int n = 1;
   for (QefDocFileIt i = files.begin(); i ≠ files.end(); ++i, ++n)
   {
      if (!output_file (**i, n, (*i)->secnum, (*i)->filenum))
         return 2;
   }

   output_footer (ous);

6.3.21
   close_files();
   return 0;
}

QefDocFile::QefDocFile (const string &name, const string &sec, int secn,
                        int file)
   : filename (name), section (sec), secnum (secn), filenum (file)
{
   if (filename.size() > 4 && filename.substr (filename.size() - 4) == ".qef")
      preproc_filename = filename.substr (0, filename.size() - 4);
   else
      preproc_filename = filename;

   language = LANG_Text;
}

6.3.22

A function to parse the simple index files.

void
parse_index (const char *filename)
{
   FILE *ins = open_input_file (filename);
   string line, secname;
   int secn = 0, filen = 1;

   while (read_line (ins, line))
   {
      if (line.empty() || line[0] == '#')
         ;
      else if (line.find ("section ") == 0)
      {
         secname = line.substr (8);
         if (secname.empty())
            parse_error ("missing section name", 5);
         ++secn;
         filen = 1;
      }
      else if (line.find ("qefdoc ") == 0)
      {
         string inpfilename = line.substr (7);
         if (inpfilename.empty())
            parse_error ("missing file name", 5);
         files.push_back (new QefDocFile (inpfilename, secname, secn, filen));
         ++filen;
      }
      else if (line.find ("title ") == 0)
      {
         string title = line.substr (6);
         if (!main_title.empty())
            parse_error ("title specified more than once", 4);
         if (title.empty())
            parse_error ("missing title", 5);
         main_title = title;
      }
      else
         parse_error ("bad line", 5);

      line = "";
      ++line_num;
   }

   if (ins ≠ stdin)
      fclose (ins);
}

6.3.23

An enumeration of the states through which the parser below can pass. `S_HEADER_MAGIC' is the initial state. Each state is explained in the switch statement in `parse'.

enum States
{
   S_HEADER_MAGIC,
   S_HEADER_LANG,
   S_HEADER_SKIP,       // This should go eventually.
   S_BEFORE_CHUNK_DOC,
   S_BEFORE_CHUNK_SRC,
   S_NEW_CHUNK_AT,
   S_DOC_LINE,
   S_SOURCE,
   S_SOURCE_AT,
   S_INLINE_COMMAND,
   S_COMMAND_NAME,
   S_COMMAND_ARG,
   S_SKIP_LINE
};

6.3.24

Parse the file one character at a time and output TEX commands to present it.

void
parse (QefDocFile &file)
{

6.3.25

Open the input file, or use `stdin' if none was specified.

   fprintf (stderr, "Parsing `%s'.\n", file.filename.c_str());
   FILE *ins = open_input_file (file.filename.c_str());

   string id, arg, doc, src;
   States state = S_HEADER_MAGIC;

   while (true)
   {

6.3.26

Read a single character from the input file and check for errors or EOF.

      int c_int = fgetc (ins);
      unsigned char c = c_int;

      if (c_int == EOF)
      {
         if (ferror (ins))
            parse_error ("error reading input file", 2);
         if (!src.empty() || !doc.empty())
            new_chunk (file, doc, src);
         if (ins ≠ stdin)
            fclose (ins);
         return;
      }

6.3.27
      if (c == '\n')
         ++line_num;

      switch (state)
      {

6.3.28

Read the identifier at the start of the file, which should be `qefdoc'. If its not then the file is of the wrong type.

The magic identifier `qefdoc' is followed by a `:' if there is information after it which needs to be processed, or by `\n' otherwise.

       case S_HEADER_MAGIC:
         if (c == ':' || c == '\n')
         {
            if (id ≠ "qefdoc")
               parse_error ("this does not seem to be a QefDoc file");
            id = "";
            state = (c == ':' ? S_HEADER_LANG : S_BEFORE_CHUNK_DOC);
         }
         else
            id += c;
         break;

6.3.29

Now we read in the name of the language which the source code is in. It might be prefixed by whitespace, and it might not exist. If it is `-' then we leave it unaltered.

       case S_HEADER_LANG:
         if (c == '\n')
         {
            if (!id.empty())
               file.set_language (id);
            id = "";
            state = S_BEFORE_CHUNK_DOC;
         }
         else if (isspace (c))
         {
            // If `id' is empty then we are still skipping space before it.
            if (!id.empty())
            {
               file.set_language (id);
               id = "";
               state = S_HEADER_SKIP;
            }
         }
         else
            id += c;
         break;

6.3.30

A kludge to ignore the stuff after `qefdoc:'. Eventually we will need to take more notice of it.

       case S_HEADER_SKIP:
         if (c == '\n')
            state = S_BEFORE_CHUNK_DOC;
         break;

6.3.31

In this state we expect either a line of source code or a line of documentation. Either way, it will be the start of a new chunk. Bits of whitespace are stored in id so that the indentation will be known if a line of source is found.

       case S_BEFORE_CHUNK_DOC:
         if (c == '\n')
         {
            id = "";
            if (!doc.empty())
               state = S_BEFORE_CHUNK_SRC;
         }
         else
         {
            id += c;
            if (c == '@')
            {
               id = "";
               state = S_NEW_CHUNK_AT;
            }
            else if (!isspace (c))
            {
               src = id;
               id = "";
               state = S_SOURCE;
            }
         }
         break;

6.3.32

This state is entered after a line of source code. From here there will either be more source or a new chunk will begin with `@'.

       case S_BEFORE_CHUNK_SRC:
         if (c == '@')
         {
            new_chunk (file, doc, src);
            id = doc = src = "";
            state = S_NEW_CHUNK_AT;
         }
         else if (isspace (c))
            id += c;
         else
         {
            src += id;
            src += c;
            id = "";
            state = S_SOURCE;
         }
         break;

6.3.33

This state is entered just after an `@' at the start of a new chunk.

       case S_NEW_CHUNK_AT:
         if (c == '\n')
         {
            if (!doc.empty())
               doc += '\n';
            state = S_BEFORE_CHUNK_DOC;
         }
         else if (isspace (c))
         {
            if (!doc.empty())
            {
               if (use_html && doc[doc.size() - 1] == '\n')
                  doc += "<p>";
               else
                  doc += '\n';
            }
            state = S_DOC_LINE;
         }
         else     // TODO: check that its a valid start of identifier.
         {
            id += c;
            state = S_SKIP_LINE;    // TODO: understand these things.
         }
         break;

6.3.34

Read a line of documentation after a `@' into doc.

       case S_DOC_LINE:
         if (c == '\n')
            state = S_BEFORE_CHUNK_DOC;
         else if (c == '@')
            state = S_INLINE_COMMAND;
         else if (isalnum (c))
            doc += c;
         else
         {
            format_text (c, id);
            doc += id;
            id = "";
         }
         break;

6.3.35

While in this state a line of source code is being read into src.

       case S_SOURCE:
         if (c == '\n')
         {
            src += '\n';
            state = S_BEFORE_CHUNK_SRC;
         }
         else if (c == '@')
            state = S_SOURCE_AT;
         else
            src += c;
         break;

6.3.36

Parse whatever follows `@' in source code.

       case S_SOURCE_AT:
         if (c == '@')
         {
            src += '@';
            state = S_SOURCE;
         }
         else
            parse_error ("stray `@' found in source");
         break;

6.3.37

Decode an `@' command which appears in a line of documentation or source code. We only stay in this state for one character.

       case S_INLINE_COMMAND:
         if (c == '@')
         {
            doc += '@';
            state = S_DOC_LINE;
         }
         else if (c == '<')
         {
            parse_error ("there must be nothing but whitespace on a line "
                         "before `@<'");
         }
         else if (isalpha (c) || c == '_')
         {
            id = c;
            state = S_COMMAND_NAME;
         }
         break;

6.3.38

Read the name of an inline `@' command.

       case S_COMMAND_NAME:
         if (isalnum (c) || c == '_')
            id += c;
         else if (c == '<')
         {
            arg = "";
            state = S_COMMAND_ARG;
         }
         else
         {
            ungetc (c, ins);
            inline_command (id, "", doc);
            id = "";
            state = S_DOC_LINE;
         }
         break;

6.3.39

Read the argument of a command, between the `<' and `>'.

       case S_COMMAND_ARG:
         if (c == '>')        // TODO: maybe add a way to escape these.
         {
            inline_command (id, arg, doc);
            id = "";
            state = S_DOC_LINE;
         }
         else
            arg += c;
         break;

6.3.40

This is a hack.

       case S_SKIP_LINE:
         if (c == '\n')
         {
            id = "";
            state = S_BEFORE_CHUNK_DOC;
         }
         break;

6.3.41

This will crash the program with an error message, but it should never happen.

       default:
         assert (false);
      }
   }
}

6.3.42

Print an error message with the filename and line number on which it was detected.

void
parse_error (const char *msg, int code)
{
   fprintf (stderr, "%s:%d: %s.\n",
            (current_filename ? current_filename : "<stdin>"), line_num, msg);

   exit (code);
}

6.3.43
bool
read_line (FILE *ins, string &line)
{
   int c = fgetc (ins);

   while (c ≠ '\n' && c ≠ EOF)
   {
      line += (char) c;
      c = fgetc (ins);
   }

   return !(c == EOF && line.empty());
}

6.3.44
void
inline_command (const string &com, const string &arg, string &s)
{

6.3.45

Typeset the TEX logo.

   if (com == "TeX")
   {
      arg.empty() || arg_error (com, false);
      s += use_html ? "T<sub><big>E</big></sub>X" : "\\TeX{}";
   }

6.3.46

Typeset the LATEX logo.

   else if (com == "LaTeX")
   {
      arg.empty() || arg_error (com, false);
      s += use_html ? "L<sup>A</sup>T<sub><big>E</big></sub>X" :
                      "\\LaTeX{}";
   }

6.3.47
   else if (com == "code")
   {
      arg.empty() && arg_error (com, true);
      s += style_start (STYLE_CODE);
      s += format_text (arg);
      s += style_end (STYLE_CODE);
   }
   else if (com == "var")
   {
      arg.empty() && arg_error (com, true);
      s += style_start (STYLE_VAR);
      s += format_text (arg);
      s += style_end (STYLE_VAR);
   }
   else if (com == "val")
   {
      arg.empty() && arg_error (com, true);
      s += style_start (STYLE_VAL);
      s += format_text (arg);
      s += style_end (STYLE_VAL);
   }
   else if (com == "func" || com == "prog" || com == "reffun" || com == "type")
   {
      arg.empty() && arg_error (com, true);
      s += style_start (STYLE_FUNC);
      s += format_text (arg);
      s += style_end (STYLE_FUNC);
   }

   // TODO: make this `ac' and use the appropriate HTML tag for it.
   else if (com == "ac")
   {
      arg.empty() && arg_error (com, true);
      s += style_start (STYLE_SMALL_CAPS);
      if (!use_html)
      {
         // TODO: put this in a function.
         string lcname = arg;
         for (unsigned int i = 0; i < lcname.size(); ++i)
            lcname[i] = tolower (lcname[i]);
         s += format_text (lcname);
      }
      else
         s += format_text (arg);
      s += style_end (STYLE_SMALL_CAPS);
   }
   else if (com == "path" || com == "file")
   {
      arg.empty() && arg_error (com, true);
      s += style_start (STYLE_PATH);
      s += format_text (arg);
      s += style_end (STYLE_PATH);
   }
   else if (com == "dots")
   {
      arg.empty() || arg_error (com, false);
      s += use_html ? "&hellip;" : "\\dots{}";
   }

6.3.48

Typeset a list of bullet points or an item such a the list.

   else if (com == "list")
   {
      arg.empty() || arg_error (com, false);
      s += use_html ? "<ul>" : "\\begin{itemize}";
   }
   else if (com == "listend")
   {
      arg.empty() || arg_error (com, false);
      s += use_html ? "</ul>" : "\\end{itemize}";
   }
   else if (com == "item")
   {
      arg.empty() || arg_error (com, false);
      s += use_html ? "<li>" : "\\item ";
   }

6.3.49

Print an error message if any other command is used.

   else
      fprintf (stderr, "%s:%d: unknown command `@%s' ignored.\n",
               current_filename, line_num, com.c_str());
}

6.3.50

Set the source language to that specified by name. The names are case-insensitive. If the name is empty or is `-' or `text' then we switch to source language independent mode.

void
QefDocFile::set_language (const string &name)
{
   // TODO: put this in a function.
   string lcname = name;
   for (unsigned int i = 0; i < name.size(); ++i)
      lcname[i] = tolower (lcname[i]);

   if (lcname.empty() || lcname == "-" || lcname == "text")
      language = LANG_Text;
   else if (lcname == "c++" || lcname == "c")   // Treat C and C++ the same.
      language = LANG_Cxx;
   else if (lcname == "lex" || lcname == "flex")
      language = LANG_Lex;
   else if (lcname == "perl")
      language = LANG_Perl;
   else if (lcname == "python")
      language = LANG_Python;
   else if (lcname == "sed")
      language = LANG_Sed;
   else
   {
      fprintf (stderr, "%s:%d: the language `%s' is unrecognized.\n",
               current_filename, line_num, name.c_str());
      exit (4);
   }
}

6.3.51

Print an error message to indicate the presence of a parameter to a command when there should not be one (if args_wanted is `true'), or the absence of a parameter when one is required (if args_wanted is `false').

The return value is always `false', to allow `arg_error' to be used in kludgy ways.

bool
arg_error (const string &com, bool args_wanted)
{
   const char *reason;
   if (args_wanted)
      reason = "ignored because it lacks a parameter";
   else
      reason = "should not have a parameter";

   fprintf (stderr, "%s:%d: command `@%s' %s.\n",
            current_filename, line_num, com.c_str(), reason);

   return false;
}

6.3.52

Add a chunk of documentation and source to the list.

void
new_chunk (QefDocFile &file, const string &doc, const string &src)
{
   string new_doc = doc, new_src = src;

   trim_end (new_doc);
   trim_end (new_src);

   file.chunks.push_back (new Chunk (new_doc, new_src));
}

6.3.53
QefDocFile *
find_file_from_index (const string &filename)
{
   for (QefDocFileIt i = files.begin(); i ≠ files.end(); ++i)
      if ((*i)->preproc_filename == filename)
         return *i;

   return 0;
}

6.3.54

TODO: don't do DOCTYPE if option_no_header.

void
output_header (FILE *ous, const string &title)
{

6.3.55

Output some helpful comments so that people reading the output file know where it came from.

   if (use_html)
      fputs ("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\""
             " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
             "<!-- This is HTML output from qweave, version " VERSION
             ". -->\n\n", ous);
   else
      fputs ("% This is LaTeX output from qweave, version " VERSION ".\n\n",
             ous);

6.3.56
   if (!option_no_header)
   {
      // TODO: set the title properly.
      // TODO: clean up the Icelandic characters.
      if (use_html)
         fprintf (ous, "<html>\n<head>\n <title>%s</title>\n"
                  " <link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">\n"
                  "</head>\n<body>\n", title.c_str(), style_sheet_name);
      else
         fputs ("\\documentclass[oneside]{report}\n"
                "\\usepackage{path}\n"
                "\\begin{document}\n", ous);
   }

   if (!use_html)
      fputs ("\\font\\ttbf=cmttb10\\def\\textttbf#1{{\\ttbf{}#1}}\n"
             "\\font\\slbf=cmbxsl10\\def\\textslbf#1{{\\slbf{}#1}}\n"
             "\\def\\tilde{\\lower 0.3em\\hbox{\\~{}}}\n"
             "\\font\\imrten=qefdocimr10\n"
             "\\font\\imrtenit=qefdocimti10\n"
             "\\def\\thorn{{\\imrten\\char\"1C}}\n"
             "\\def\\Thorn{{\\imrten\\char\"1E}}\n"
             "\\def\\eth{{\\imrten\\char\"1F}}\n"
             "\\def\\Eth{{\\imrten\\char\"20}}\n"
             "\\def\\ithorn{{\\imrtenit\\char\"1C}}\n"
             "\\def\\iThorn{{\\imrtenit\\char\"1E}}\n"
             "\\def\\ieth{{\\imrtenit\\char\"1F}}\n"
             "\\def\\iEth{{\\imrtenit\\char\"20}}\n"
             "\\def\\line#1{\\hbox to\\hsize{#1}}\n\n", ous);
}

6.3.57
void
output_toc ()
{
   if (use_html)
   {
      fputs ("<h1>Table of Contents</h1>\n<ul>\n", ous);

      int n = 1;
      string secname;
      bool in_ul = false;
      for (QefDocFileIt i = files.begin(); i ≠ files.end(); ++i, ++n)
      {
         if ((*i)->secnum && (*i)->section ≠ secname)
         {
            secname = (*i)->section;
            if (in_ul)
               fputs (" </ul>\n", ous);
            fprintf (ous, " <li>%d. %s\n <ul>\n", (*i)->secnum,
                     secname.c_str());
            in_ul = true;
         }

         char chunknum[256];
         chunknum[0] = '\0';
         if ((*i)->secnum)
            sprintf (chunknum, "%d.", (*i)->secnum);
         sprintf (chunknum + strlen (chunknum), "%d.", (*i)->filenum);

         if (option_split)
            fprintf (ous, "  <li>%s <a href=\"%s.html\">%s</a>\n", chunknum,
                     (*i)->preproc_filename.c_str(),
                     (*i)->preproc_filename.c_str());
         else
            fprintf (ous, "  <li>%s <a href=\"#F%d\">%s</a>\n", chunknum, n,
                     (*i)->preproc_filename.c_str());
      }

      if (in_ul)
         fputs (" </ul>\n", ous);
      fputs ("</ul>\n", ous);
   }
   else
      fputs ("\\tableofcontents\\clearpage\n", ous);
}

6.3.58

This function prints all of the actual TEX or HTML code for a single input file. By the time its called the input file will have been completely read and digested. chnsec is the section number this file is part of, or `0' if it isn't in one.

bool
output_file (QefDocFile &file, int refn, int chnsec, int chnfile)
{

6.3.59

Choose the file to output to.

   FILE *ous = ::ous;
   if (use_html && option_split)
   {
      string htmloutfile = file.preproc_filename + ".html";
      ous = fopen (htmloutfile.c_str(), "w");
      if (!ous)
      {
         fprintf (stderr, "qweave: couldn't open output file `%s'.\n",
                  htmloutfile.c_str());
         return false;
      }

      output_header (ous, file.preproc_filename);
   }

6.3.60
   char chunknum[256];  // The full 2 or 3 part chunk number.
   chunknum[0] = '\0';
   if (chnsec)
      sprintf (chunknum, "%d.", chnsec);
   if (chnfile)
      sprintf (chunknum + strlen (chunknum), "%d.", chnfile);

6.3.61

If this is a new section then output its title.

   static int oldsec = -1;
   if (chnsec ≠ oldsec && !option_no_header)
   {
      if (use_html)
         fprintf (ous, "<h1>%d. %s</h1>\n", chnsec, file.section.c_str());
      else
         fprintf (ous, "\\chapter{%s}\n", file.section.c_str());
   }
   oldsec = chnsec;

6.3.62

The title of the file and the anchor/label for it to link to.

   string title = (file.title.empty() ? file.preproc_filename : file.title);
   if (use_html)
      fprintf (ous, "\n<!-- Start of file `%s'. -->\n"
                    "<h3><a name=\"F%d\"></a>%s %s</h3>\n",
                    file.filename.c_str(), refn, chunknum, title.c_str());
   else
      fprintf (ous, "\n%% Start of file `%s'.\n"
                    "\\section{%s}\n",
                    file.filename.c_str(), format_text (title, false).c_str());

6.3.63

Output the chunks of documentation and/or code, one by one. They are numbered by section (chnsec, if specified), file (chnfile) and chunk within file (chn) starting from `1.1' or `1.1.1'. Before each chunk a comment is printed into the output file for debugging purposes.

   int chn = 1;
   list <const Chunk *>::const_iterator i;
   for (i = file.chunks.begin(); i ≠ file.chunks.end(); ++i, ++chn)
   {
      if (use_html)
         fprintf (ous, "\n<!-- Chunk %s%d. -->\n"
                  "<table width=\"100%%\" border=0><tr><td width=\"70%%\">\n"
                  " <td width=\"30%%\">\n"
                  "  <table width=\"100%%\" border=0><tr><td><hr noshade>\n"
                  "   <td width=\"10%%\">%s%d</table>\n"
                  " </table>\n", chunknum, chn, chunknum, chn);
      else
         fprintf (ous, "\n%% Chunk %s%d.\n"
                  "\\vskip 5pt\\line{\\hfill\\hbox to 0.23\\hsize{"
                  "\\hrulefill~%s%d}}\\vskip 5pt\n",
                  chunknum, chn, chunknum, chn);

6.3.64

Output the documentation, if there was any in this chunk.

      if (!(*i)->doc.empty())
      {
         fputs (use_html ? "<p>" : "\\par{}", ous);
         fputs ((*i)->doc.c_str(), ous);
         if (!(*i)->src.empty())
            fputs (use_html ? "" : "\\vskip 5pt\n", ous);
      }

6.3.65

Output the source code, if there was any in this chunk. Before being printed it is run through the appropriate pretty printer.

      if (!(*i)->src.empty())
      {
         const string &s = pretty_print (file.language, (*i)->src);
         // The \hsize thing is a dirty hack for now.
         if (use_html)
            fputs ("<pre>", ous);
         else
            fputs ("\\begin{flushleft}{\\tt\\obeylines\\hsize=23cm%\n", ous);
         fputs (s.c_str(), ous);
         if (use_html)
            fputs ("</pre>", ous);
         else
            fputs ("\n}\\end{flushleft}", ous);
      }
      fputs ("\n", ous);
   }

   if (ous ≠ ::ous)
   {
      output_footer (ous);
      fclose (ous);
   }

   return true;
}

6.3.66

Finish off the output file.

void
output_footer (FILE *ous)
{
   if (!option_no_header)
      fputs (use_html ? "\n\n</body>\n</html>\n"
                      : "\n\n\\end{document}\n", ous);
   fputs (use_html ? "<!--" : "%", ous);
   fputs (" Here ends the output of qweave.", ous);
   fputs (use_html ? " -->\n" : "\n", ous);
}

6.3.67

Use whichever pretty printer is suitable for the source code language lang to process the code in src.

const string &
pretty_print (Language lang, const string &src)
{
   if (lang == LANG_Cxx)
      return pretty_print_cxx (src);
   else if (lang == LANG_Python)
      return pretty_print_python (src);
   else if (lang == LANG_Text)
      return pretty_print_text (src);
   else
   {
//      fprintf (stderr, "%s:%d: no pretty printer is available for the language"
//               " `%s'.\n", current_filename, line_num,
//               language_names[lang - 1]);
      return pretty_print_text (src);
   }
}

6.3.68

TextStyleTypes provides indices into the array style_code.

style_code is a table of scraps of formating code in TEX and HTML, which are used by the function below.

TODO: use a style sheet, maybe, for the HTML.

const char *style_code[][4] =
{
   { "<span class=\"keyword\">", "\\textttbf{", "</span>", "}" },
   { "<span class=\"constant\">", "\\textit{", "</span>", "}" },
   { "<span class=\"comment\">", "\\textsl{", "</span>", "}" },
   { "<code>", "\\texttt{", "</code>", "}" },
   { "`<span class=\"val\">", "`\\textit{", "</span>'", "\\/}'" },
   { "<var>", "\\textit{", "</var>", "\\/}" },
   { "`<span class=\"func\">", "`\\textbf{", "</span>'", "}'" },
   { "<span class=\"preproc\">", "", "</span>", "" },
   { "<span class=\"incfile\">", "", "</span>", "" },
   { "<span class=\"typeword\">", "\\textttbf{", "</span>", "}" },
   { "<span class=\"string\">", "", "</span>", "" },
   { "<span class=\"path\">", "\\path|", "</span>", "|" },
   { "<span class=\"commenthl\">", "\\textslbf{", "</span>", "}" },
   { "<span class=\"stringesc\">", "\\textbf{", "</span>", "}" },
   { "<acronym>", "\\textsc{", "</acronym>", "}" }
};

6.3.69

These functions return strings containing the appropriate TEX or HTML code to start or end different types of text style. It looks the relevent strings up in the table above.

const char *
style_start (TextStyleTypes style)
{
   return style_code[style - 1][use_html ? 0 : 1];
}

const char *
style_end (TextStyleTypes style)
{
   return style_code[style - 1][use_html ? 2 : 3];
}

6.3.70

Wrap text style code round a string and return it.

string
style_text (TextStyleTypes style, const string &text)
{
   string s = style_start (style);
   s += text;
   s += style_end (style);

   return s;
}

6.3.71

Set enc to the TEX or HTML code necessary to typeset the character c.

void
format_text (char c, string &enc, bool source_mode)
{
   if (use_html)
   {
      switch (c)
      {
         case '<': enc = "&lt;";  break;
         case '>': enc = "&gt;";  break;
         case '&': enc = "&amp;";  break;
         default: enc = c;
      }
   }
   else
   {
      switch (c)
      {
         case ' ': enc = source_mode ? "\\ " : " ";  break;
         case '$': enc = "\\$";  break;
         case '#': enc = "\\#";  break;
         case '%': enc = "\\%";  break;
         case '&': enc = "\\&";  break;
         case '^': enc = "\\char\"5E{}";  break;
         case '_': enc = "\\_";  break;
         case '{': enc = "$\\lbrace$";  break;
         case '}': enc = "$\\rbrace$";  break;
         case '~': enc = "\\tilde{}";  break;
         case '<': enc = "$<$";  break;
         case '>': enc = "$>$";  break;
         case '\\': enc = "$\\backslash$";  break;

6.3.72

These are some kludges for Icelandic characters. XXX: use the alternate ones for italic, or something.

         case 'ö': enc = "\\\"{o}";  break;
         case 'ø': enc = "\\o{}";  break;
         case 'ð': enc = "\\eth{}";  break;
         case 'Ð': enc = "\\Eth{}";  break;
         case 'þ': enc = "\\thorn{}";  break;
         case 'Þ': enc = "\\Thorn{}";  break;

         default: enc = c;
      }
   }
}

6.3.73

Convert a whole string to TEX or HTML code and return it.

string
format_text (const string &s, bool source_mode)
{
   string ch, tex;

   for (unsigned int i = 0; i < s.size(); ++i)
   {
      format_text (s[i], ch, source_mode);
      tex += ch;
   }

   return tex;
}

6.3.74
FILE *
open_input_file (const char *filename)
{
   FILE *ins = stdin;
   if (filename)
   {
      ins = fopen (filename, "r");
      if (ins == NULL)
      {
         fprintf (stderr, "Error opening input file `%s'.\n", filename);
         exit (2);
      }
   }
   ::current_filename = filename;
   line_num = 1;

   return ins;
}

6.3.75

Close the input and output files if they are not stdin and stdout.

void
close_files ()
{
   if (ous && ous ≠ stdout)
      fclose (ous);
}

6.3.76

Strip off trailing whitespace from s.

void
trim_end (string &s)
{
   // Remove the space at the end one character at a time.
   while (s.size() > 0 && isspace (s[s.size() - 1]))
      s.resize (s.size() - 1);
}

6.3.77
int
find_word (const Word *words, const char *w)
{
   const Word *i = words;

   for (; i->word; ++i)
   {
      if (strcmp (w, i->word) == 0)
         return i->type;
   }

   return 0;
}