MODULES := main malloc ustring error help licence version misc tree234
MODULES += input keywords contents index style biblio
-MODULES += bk_text bk_xhtml bk_whlp
+MODULES += bk_text bk_xhtml bk_whlp bk_man
MODULES += winhelp
OBJECTS := $(addsuffix .o,$(MODULES))
--- /dev/null
+/*
+ * man page backend for Halibut
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include "halibut.h"
+
+static void man_text(FILE *, word *, int newline, int quote_props);
+static void man_codepara(FILE *, word *);
+
+#define QUOTE_INITCTRL 1 /* quote initial . and ' on a line */
+#define QUOTE_QUOTES 2 /* quote double quotes by doubling them */
+
+void man_backend(paragraph *sourceform, keywordlist *keywords,
+ indexdata *idx) {
+ paragraph *p;
+ FILE *fp;
+ char const *sep;
+
+ IGNORE(keywords); /* we don't happen to need this */
+ IGNORE(idx); /* or this */
+
+ /*
+ * Determine the output file name, and open the output file
+ *
+ * FIXME: want configurable output file names here. For the
+ * moment, we'll just call it `output.1'.
+ */
+ fp = fopen("output.1", "w");
+ if (!fp) {
+ error(err_cantopenw, "output.1");
+ return;
+ }
+
+ /* Do the version ID */
+ for (p = sourceform; p; p = p->next)
+ if (p->type == para_VersionID) {
+ fprintf(fp, ".\\\" ");
+ man_text(fp, p->words, TRUE, 0);
+ }
+
+ /* FIXME: .TH name-of-program manual-section */
+ fprintf(fp, ".TH FIXME 1\n");
+
+ fprintf(fp, ".UC\n");
+
+ /* Do the preamble and copyright */
+ sep = "";
+ for (p = sourceform; p; p = p->next)
+ if (p->type == para_Preamble) {
+ fprintf(fp, "%s", sep);
+ man_text(fp, p->words, TRUE, 0);
+ sep = "\n";
+ }
+ for (p = sourceform; p; p = p->next)
+ if (p->type == para_Copyright) {
+ fprintf(fp, "%s", sep);
+ man_text(fp, p->words, TRUE, 0);
+ sep = "\n";
+ }
+
+ /*
+ * FIXME:
+ *
+ * - figure out precisely what needs to be escaped.
+ * * A dot or apostrophe at the start of a line wants to be
+ * preceded by `\&', which is a zero-width space.
+ * * Literal backslashes always want doubling.
+ * * Within double quotes, a double quote needs doubling
+ * too.
+ *
+ * - work out what to do about hyphens / minuses...
+ */
+ for (p = sourceform; p; p = p->next) switch (p->type) {
+ /*
+ * Things we ignore because we've already processed them or
+ * aren't going to touch them in this pass.
+ */
+ case para_IM:
+ case para_BR:
+ case para_Biblio: /* only touch BiblioCited */
+ case para_VersionID:
+ case para_Copyright:
+ case para_Preamble:
+ case para_NoCite:
+ case para_Title:
+ break;
+
+ /*
+ * Headings.
+ */
+ case para_Chapter:
+ case para_Appendix:
+ case para_UnnumberedChapter:
+ case para_Heading:
+ case para_Subsect:
+ fprintf(fp, ".SH \"");
+ /* FIXME: disable this, at _least_ by default */
+ if (p->kwtext)
+ man_text(fp, p->kwtext, FALSE, QUOTE_QUOTES);
+ fprintf(fp, " ");
+ man_text(fp, p->words, FALSE, QUOTE_QUOTES);
+ fprintf(fp, "\"\n");
+ break;
+
+ /*
+ * Code paragraphs.
+ */
+ case para_Code:
+ fprintf(fp, ".PP\n");
+ man_codepara(fp, p->words);
+ break;
+
+ /*
+ * Normal paragraphs.
+ */
+ case para_Normal:
+ fprintf(fp, ".PP\n");
+ man_text(fp, p->words, TRUE, 0);
+ break;
+
+ /*
+ * List paragraphs.
+ */
+ case para_Description:
+ case para_BiblioCited:
+ case para_Bullet:
+ case para_NumberedList:
+ if (p->type == para_Bullet) {
+ fprintf(fp, ".IP \"\\fBo\\fP\"\n"); /* FIXME: configurable? */
+ } else if (p->type == para_NumberedList) {
+ fprintf(fp, ".IP \"");
+ man_text(fp, p->kwtext, FALSE, QUOTE_QUOTES);
+ fprintf(fp, "\"\n");
+ } else if (p->type == para_Description) {
+ /*
+ * Do nothing; the .xP for this paragraph is the .IP
+ * which has come before it in the DescribedThing.
+ */
+ } else if (p->type == para_BiblioCited) {
+ fprintf(fp, ".IP \"");
+ man_text(fp, p->kwtext, FALSE, QUOTE_QUOTES);
+ fprintf(fp, "\"\n");
+ }
+ man_text(fp, p->words, TRUE, 0);
+ break;
+
+ case para_DescribedThing:
+ fprintf(fp, ".IP \"");
+ man_text(fp, p->words, FALSE, QUOTE_QUOTES);
+ fprintf(fp, "\"\n");
+ break;
+
+ case para_Rule:
+ /*
+ * FIXME.
+ */
+ break;
+
+ case para_LcontPush:
+ fprintf(fp, ".RS\n");
+ break;
+ case para_LcontPop:
+ fprintf(fp, ".RE\n");
+ break;
+ }
+
+ /*
+ * Tidy up.
+ */
+ fclose(fp);
+}
+
+/*
+ * Convert a wide string into a string of chars. If `result' is
+ * non-NULL, mallocs the resulting string and stores a pointer to
+ * it in `*result'. If `result' is NULL, merely checks whether all
+ * characters in the string are feasible for the output character
+ * set.
+ *
+ * Return is nonzero if all characters are OK. If not all
+ * characters are OK but `result' is non-NULL, a result _will_
+ * still be generated!
+ *
+ * FIXME: Here is probably also a good place to do escaping sorts
+ * of things. I know I at least need to escape backslash, and full
+ * stops at the starts of words are probably trouble as well.
+ */
+static int man_convert(wchar_t *s, char **result, int quote_props) {
+ /*
+ * FIXME. Currently this is ISO8859-1 only.
+ */
+ int doing = (result != 0);
+ int ok = TRUE;
+ char *p = NULL;
+ int plen = 0, psize = 0;
+
+ for (; *s; s++) {
+ wchar_t c = *s;
+ char outc;
+
+ if ((c >= 32 && c <= 126) ||
+ (c >= 160 && c <= 255)) {
+ /* Char is OK. */
+ outc = (char)c;
+ } else {
+ /* Char is not OK. */
+ ok = FALSE;
+ outc = 0xBF; /* approximate the good old DEC `uh?' */
+ }
+ if (doing) {
+ if (plen+3 >= psize) {
+ psize = plen + 256;
+ p = resize(p, psize);
+ }
+ if (plen == 0 && (outc == '.' || outc == '\'') &&
+ (quote_props & QUOTE_INITCTRL)) {
+ /*
+ * Control character (. or ') at the start of a
+ * line. Quote it by putting \& (troff zero-width
+ * space) before it.
+ */
+ p[plen++] = '\\';
+ p[plen++] = '&';
+ } else if (outc == '\\') {
+ /*
+ * Quote backslashes by doubling them, always.
+ */
+ p[plen++] = '\\';
+ } else if (outc == '"' && (quote_props & QUOTE_QUOTES)) {
+ /*
+ * Double quote within double quotes. Quote it by
+ * doubling.
+ */
+ p[plen++] = '"';
+ }
+ p[plen++] = outc;
+ }
+ }
+ if (doing) {
+ p = resize(p, plen+1);
+ p[plen] = '\0';
+ *result = p;
+ }
+ return ok;
+}
+
+static void man_rdaddwc(rdstringc *rs, word *text, word *end,
+ int quote_props) {
+ char *c;
+
+ for (; text && text != end; text = text->next) switch (text->type) {
+ case word_HyperLink:
+ case word_HyperEnd:
+ case word_UpperXref:
+ case word_LowerXref:
+ case word_XrefEnd:
+ case word_IndexRef:
+ break;
+
+ case word_Normal:
+ case word_Emph:
+ case word_Code:
+ case word_WeakCode:
+ case word_WhiteSpace:
+ case word_EmphSpace:
+ case word_CodeSpace:
+ case word_WkCodeSpace:
+ case word_Quote:
+ case word_EmphQuote:
+ case word_CodeQuote:
+ case word_WkCodeQuote:
+ assert(text->type != word_CodeQuote &&
+ text->type != word_WkCodeQuote);
+ if (towordstyle(text->type) == word_Emph &&
+ (attraux(text->aux) == attr_First ||
+ attraux(text->aux) == attr_Only))
+ rdaddsc(rs, "\\fI");
+ else if (towordstyle(text->type) == word_Code &&
+ (attraux(text->aux) == attr_First ||
+ attraux(text->aux) == attr_Only))
+ rdaddsc(rs, "\\fB");
+ if (removeattr(text->type) == word_Normal) {
+ if (rs->pos > 0)
+ quote_props &= ~QUOTE_INITCTRL; /* not at start any more */
+ if (man_convert(text->text, &c, quote_props))
+ rdaddsc(rs, c);
+ else
+ man_rdaddwc(rs, text->alt, NULL, quote_props);
+ sfree(c);
+ } else if (removeattr(text->type) == word_WhiteSpace) {
+ rdaddc(rs, ' ');
+ } else if (removeattr(text->type) == word_Quote) {
+ rdaddc(rs, quoteaux(text->aux) == quote_Open ? '`' : '\'');
+ /* FIXME: configurability */
+ }
+ if (towordstyle(text->type) == word_Emph &&
+ (attraux(text->aux) == attr_Last ||
+ attraux(text->aux) == attr_Only))
+ rdaddsc(rs, "\\fP");
+ else if (towordstyle(text->type) == word_Code &&
+ (attraux(text->aux) == attr_Last ||
+ attraux(text->aux) == attr_Only))
+ rdaddsc(rs, "\\fP");
+ break;
+ }
+}
+
+static void man_text(FILE *fp, word *text, int newline, int quote_props) {
+ rdstringc t = { 0, 0, NULL };
+
+ man_rdaddwc(&t, text, NULL, quote_props | QUOTE_INITCTRL);
+ fprintf(fp, "%s", t.text);
+ sfree(t.text);
+ if (newline)
+ fputc('\n', fp);
+}
+
+static void man_codepara(FILE *fp, word *text) {
+ fprintf(fp, ".nf\n");
+ for (; text; text = text->next) if (text->type == word_WeakCode) {
+ char *c;
+ man_convert(text->text, &c, QUOTE_INITCTRL);
+ fprintf(fp, "%s\n", c);
+ sfree(c);
+ }
+ fprintf(fp, ".fi\n");
+}
word spaceword;
FILE *fp;
char *prefixextra;
+ int nesting, nestindent;
int indentb, indenta;
IGNORE(keywords); /* we don't happen to need this */
conf.indent_preambles ? conf.indent : 0, 0,
conf.width + (conf.indent_preambles ? 0 : conf.indent));
+ nestindent = conf.listindentbefore + conf.listindentafter;
+ nesting = 0;
+
/* Do the main document */
for (p = sourceform; p; p = p->next) switch (p->type) {
+ case para_LcontPush:
+ nesting++;
+ break;
+ case para_LcontPop:
+ assert(nesting > 0);
+ nesting--;
+ break;
+
/*
* Things we ignore because we've already processed them or
* aren't going to touch them in this pass.
break;
case para_Rule:
- text_rule(fp, conf.indent, conf.width);
+ text_rule(fp, conf.indent + nestindent*nesting,
+ conf.width - nestindent*nesting);
break;
case para_Normal:
+ case para_DescribedThing:
+ case para_Description:
case para_BiblioCited:
case para_Bullet:
case para_NumberedList:
prefixextra = "."; /* FIXME: configurability */
indentb = conf.listindentbefore;
indenta = conf.listindentafter;
+ } else if (p->type == para_Description) {
+ prefix = NULL;
+ prefixextra = NULL;
+ indentb = conf.listindentbefore;
+ indenta = conf.listindentafter;
} else {
prefix = NULL;
prefixextra = NULL;
body = p->words;
}
text_para(fp, prefix, prefixextra, body,
- conf.indent + indentb, indenta,
- conf.width - indentb - indenta);
+ conf.indent + nestindent*nesting + indentb, indenta,
+ conf.width - nestindent*nesting - indentb - indenta);
if (wp) {
wp->next = NULL;
free_word_list(body);
break;
case para_Code:
- text_codepara(fp, p->words, conf.indent + conf.indent_code, conf.width - 2 * conf.indent_code);
+ text_codepara(fp, p->words,
+ conf.indent + nestindent*nesting + conf.indent_code,
+ conf.width - nestindent*nesting - 2 * conf.indent_code);
break;
}
struct bk_whlp_state state;
WHLP_TOPIC contents_topic;
int i;
+ int nesting;
indexentry *ie;
filename = "output.hlp"; /* FIXME: configurability */
* Now we've done the contents page, we're ready to go through
* and do the main manual text. Ooh.
*/
+ nesting = 0;
for (p = sourceform; p; p = p->next) switch (p->type) {
/*
* Things we ignore because we've already processed them or
case para_Title:
break;
+ case para_LcontPush:
+ nesting++;
+ break;
+ case para_LcontPop:
+ assert(nesting > 0);
+ nesting--;
+ break;
+
/*
* Chapter and section titles: start a new Help topic.
*/
break;
case para_Normal:
+ case para_DescribedThing:
+ case para_Description:
case para_BiblioCited:
case para_Bullet:
case para_NumberedList:
whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12);
if (p->type == para_Bullet || p->type == para_NumberedList) {
- whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72);
+ whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting + 72);
whlp_para_attr(h, WHLP_PARA_FIRSTLINEINDENT, -36);
whlp_set_tabstop(h, 72, WHLP_ALIGN_LEFT);
whlp_begin_para(h, WHLP_PARA_SCROLL);
}
whlp_tab(h);
} else {
+ whlp_para_attr(h, WHLP_PARA_LEFTINDENT,
+ 72*nesting + (p->type==para_Description ? 72 : 0));
whlp_begin_para(h, WHLP_PARA_SCROLL);
}
for (w = p->words; w; w = w->next) {
if (!w->next)
whlp_para_attr(h, WHLP_PARA_SPACEBELOW, 12);
+ whlp_para_attr(h, WHLP_PARA_LEFTINDENT, 72*nesting);
whlp_begin_para(h, WHLP_PARA_SCROLL);
whlp_set_font(h, FONT_CODE);
whlp_convert(w->text, &c, FALSE);
/* Write this list of paragraphs. Close off all lists at the end. */
static void xhtml_do_paras(FILE *fp, paragraph *p)
{
- int last_type = -1, first=TRUE;
+ int last_type = -1, ptype, first=TRUE;
+ stack lcont_stack = stk_new();
if (!p)
return;
/* for (; p && (xhtml_para_level(p)>limit || xhtml_para_level(p)==-1 || first); p=p->next) {*/
for (; p && (xhtml_para_level(p)==-1 || first); p=p->next) {
first=FALSE;
- switch (p->type)
+ switch (ptype = p->type)
{
/*
* Things we ignore because we've already processed them or
fprintf(fp, "</p>\n");
break;
+ case para_LcontPush:
+ {
+ int *p;
+ p = mknew(int);
+ *p = last_type;
+ stk_push(lcont_stack, p);
+ last_type = para_Normal;
+ }
+ break;
+ case para_LcontPop:
+ {
+ int *p = stk_pop(lcont_stack);
+ assert(p);
+ ptype = last_type = *p;
+ sfree(p);
+ goto closeofflist; /* ick */
+ }
+ break;
+
case para_Bullet:
case para_NumberedList:
+ case para_Description:
+ case para_DescribedThing:
case para_BiblioCited:
if (last_type!=p->type) {
/* start up list if necessary */
fprintf(fp, "<ul>\n");
} else if (p->type == para_NumberedList) {
fprintf(fp, "<ol>\n");
- } else if (p->type == para_BiblioCited) {
+ } else if (p->type == para_BiblioCited ||
+ p->type == para_DescribedThing ||
+ p->type == para_Description) {
fprintf(fp, "<dl>\n");
}
}
- if (p->type == para_Bullet || p->type == para_NumberedList)
+ if (p->type == para_Bullet || p->type == para_NumberedList) {
fprintf(fp, "<li>");
- else if (p->type == para_BiblioCited) {
+ } else if (p->type == para_DescribedThing) {
+ fprintf(fp, "<dt>");
+ } else if (p->type == para_Description) {
+ fprintf(fp, "<dd>");
+ } else if (p->type == para_BiblioCited) {
fprintf(fp, "<dt>");
xhtml_para(fp, p->kwtext);
fprintf(fp, "</dt>\n<dd>");
}
xhtml_para(fp, p->words);
- if (p->type == para_BiblioCited) {
+ {
+ paragraph *p2 = p->next;
+ if (p2 && xhtml_para_level(p2)==-1 && p2->type == para_LcontPush)
+ break;
+ }
+
+ closeofflist:
+ if (ptype == para_BiblioCited) {
fprintf(fp, "</dd>\n");
- } else if (p->type == para_Bullet || p->type == para_NumberedList) {
+ } else if (p->type == para_DescribedThing) {
+ fprintf(fp, "</dt>");
+ } else if (p->type == para_Description) {
+ fprintf(fp, "</dd>");
+ } else if (ptype == para_Bullet || ptype == para_NumberedList) {
fprintf(fp, "</li>");
}
- if (p->type == para_Bullet || p->type == para_NumberedList || p->type == para_BiblioCited)
+ if (ptype == para_Bullet || ptype == para_NumberedList ||
+ ptype == para_BiblioCited || ptype == para_Description ||
+ ptype == para_DescribedThing)
/* close off list if necessary */
{
paragraph *p2 = p->next;
int close_off=FALSE;
/* if (p2 && (xhtml_para_level(p2)>limit || xhtml_para_level(p2)==-1)) {*/
if (p2 && xhtml_para_level(p2)==-1) {
- if (p2->type != p->type)
+ if (p2->type != ptype && p2->type != para_LcontPush)
close_off=TRUE;
} else {
close_off=TRUE;
}
if (close_off) {
- if (p->type == para_Bullet) {
+ if (ptype == para_Bullet) {
fprintf(fp, "</ul>\n");
- } else if (p->type == para_NumberedList) {
+ } else if (ptype == para_NumberedList) {
fprintf(fp, "</ol>\n");
- } else if (p->type == para_BiblioCited) {
+ } else if (ptype == para_BiblioCited ||
+ ptype == para_Description ||
+ ptype == para_DescribedThing) {
fprintf(fp, "</dl>\n");
}
}
xhtml_codepara(fp, p->words);
break;
}
- last_type = p->type;
+ last_type = ptype;
}
+
+ stk_free(lcont_stack);
}
/*
sprintf(error, "unclosed braces at end of paragraph");
flags = FILEPOS;
break;
+ case err_missingrbrace2:
+ fpos = *va_arg(ap, filepos *);
+ sprintf(error, "unclosed braces at end of input file");
+ flags = FILEPOS;
+ break;
case err_nestedstyles:
fpos = *va_arg(ap, filepos *);
sprintf(error, "unable to nest text styles");
sprintf(error + strlen(error), "%s:%d", fpos2.filename, fpos2.line);
flags = FILEPOS;
break;
+ case err_misplacedlcont:
+ fpos = *va_arg(ap, filepos *);
+ sprintf(error, "\\lcont is only expected after a list item");
+ flags = FILEPOS;
+ break;
+ case err_sectmarkerinlcont:
+ fpos = *va_arg(ap, filepos *);
+ sprintf(error, "section headings are not supported within \\lcont");
+ flags = FILEPOS;
+ break;
case err_whatever:
sp = va_arg(ap, char *);
vsprintf(error, sp, ap);
para_BiblioCited, /* ... into this paragraph type */
para_Bullet,
para_NumberedList,
+ para_DescribedThing,
+ para_Description,
para_Code,
para_Copyright,
para_Preamble,
para_Title,
para_VersionID,
para_Config, /* configuration directive */
+ para_LcontPush, /* begin continuation of list item */
+ para_LcontPop, /* end continuation of list item */
para_NotParaType /* placeholder value */
};
err_commenteof, /* EOF inside braced comment */
err_kwexprbr, /* expected `}' after cross-ref */
err_missingrbrace, /* unclosed braces at end of para */
+ err_missingrbrace2, /* unclosed braces at end of file */
err_nestedstyles, /* unable to nest text styles */
err_nestedindex, /* unable to nest `\i' thingys */
err_nosuchkw, /* unresolved cross-reference */
err_sectjump, /* jump a heading level, eg \C -> \S */
err_winhelp_ctxclash, /* WinHelp context ID hash clash */
err_multikw, /* keyword clash in sections */
+ err_misplacedlcont, /* \lcont not after a list item */
+ err_sectmarkerinlcont, /* section marker appeared in \lcont */
err_whatever /* random error of another type */
};
void stk_free(stack);
void stk_push(stack, void *);
void *stk_pop(stack);
+void *stk_top(stack);
typedef struct tagRdstring rdstring;
struct tagRdstring {
*/
void whlp_backend(paragraph *, keywordlist *, indexdata *);
+/*
+ * bk_man.c
+ */
+void man_backend(paragraph *, keywordlist *, indexdata *);
+
#endif
c_copyright, /* copyright statement */
c_cw, /* weak code */
c_date, /* document processing date */
+ c_dd, /* description list: description */
c_define, /* macro definition */
+ c_dt, /* description list: described thing */
c_e, /* emphasis */
c_i, /* visible index mark */
c_ii, /* uncapitalised visible index mark */
c_k, /* uncapitalised cross-reference */
+ c_lcont, /* continuation para(s) for list item */
c_n, /* numbered list */
c_nocite, /* bibliography trickery */
c_preamble, /* document preamble text */
{"copyright", c_copyright}, /* copyright statement */
{"cw", c_cw}, /* weak code */
{"date", c_date}, /* document processing date */
+ {"dd", c_dd}, /* description list: description */
{"define", c_define}, /* macro definition */
+ {"dt", c_dt}, /* description list: described thing */
{"e", c_e}, /* emphasis */
{"i", c_i}, /* visible index mark */
{"ii", c_ii}, /* uncapitalised visible index mark */
{"k", c_k}, /* uncapitalised cross-reference */
+ {"lcont", c_lcont}, /* continuation para(s) for list item */
{"n", c_n}, /* numbered list */
{"nocite", c_nocite}, /* bibliography trickery */
{"preamble", c_preamble}, /* document preamble text */
int already;
int iswhite, seenwhite;
int type;
+ int prev_para_type;
struct stack_item {
enum {
stack_nop = 0, /* do nothing (for error recovery) */
word **idximplicit; /* to restore from \u alternatives */
} *sitem;
stack parsestk;
+ struct crossparaitem {
+ int type; /* currently c_lcont or -1 */
+ int seen_lcont;
+ };
+ stack crossparastk;
word *indexword, *uword, *iword;
word *idxwordlist;
rdstring indexstr;
macros = newtree234(macrocmp);
already = FALSE;
+ crossparastk = stk_new();
+
/*
* Loop on each paragraph.
*/
/*
* Get a token.
*/
- if (!already) {
- dtor(t), t = get_token(in);
- }
- already = FALSE;
+ do {
+ if (!already) {
+ dtor(t), t = get_token(in);
+ }
+ already = FALSE;
+ } while (t.type == tok_eop);
if (t.type == tok_eof)
break;
break;
else if (t.type != tok_cmd || t.cmd != c_c) {
error(err_brokencodepara, &t.pos);
+ prev_para_type = par.type;
addpara(par, ret);
while (t.type != tok_eop) /* error recovery: */
dtor(t), t = get_token(in); /* eat rest of paragraph */
goto codeparabroken; /* ick, but such is life */
}
}
+ prev_para_type = par.type;
addpara(par, ret);
codeparabroken:
continue;
}
/*
+ * Spot the special commands that define a grouping of more
+ * than one paragraph, and also the closing braces that
+ * finish them.
+ */
+ if (t.type == tok_cmd &&
+ t.cmd == c_lcont) {
+ struct crossparaitem *sitem, *stop;
+
+ /*
+ * Expect, and swallow, an open brace.
+ */
+ dtor(t), t = get_token(in);
+ if (t.type != tok_lbrace) {
+ error(err_explbr, &t.pos);
+ continue;
+ }
+
+ /*
+ * \lcont causes a continuation of a list item into
+ * multiple paragraphs (which may in turn contain
+ * nested lists, code paras etc). Hence, the previous
+ * paragraph must be of a list type.
+ */
+ sitem = mknew(struct crossparaitem);
+ stop = (struct crossparaitem *)stk_top(crossparastk);
+ if (prev_para_type == para_Bullet ||
+ prev_para_type == para_NumberedList ||
+ prev_para_type == para_Description) {
+ sitem->type = c_lcont;
+ sitem->seen_lcont = 1;
+ par.type = para_LcontPush;
+ prev_para_type = par.type;
+ addpara(par, ret);
+ } else {
+ /*
+ * Push a null item on the cross-para stack so that
+ * when we see the corresponding closing brace we
+ * don't give a cascade error.
+ */
+ sitem->type = -1;
+ sitem->seen_lcont = (stop ? stop->seen_lcont : 0);
+ error(err_misplacedlcont, &t.pos);
+ }
+ stk_push(crossparastk, sitem);
+ continue;
+ } else if (t.type == tok_rbrace) {
+ struct crossparaitem *sitem = stk_pop(crossparastk);
+ if (!sitem)
+ error(err_unexbrace, &t.pos);
+ else {
+ switch (sitem->type) {
+ case c_lcont:
+ par.type = para_LcontPop;
+ prev_para_type = par.type;
+ addpara(par, ret);
+ break;
+ }
+ sfree(sitem);
+ }
+ continue;
+ }
+
+ /*
* This token begins a paragraph. See if it's one of the
* special commands that define a paragraph type.
*
case c_U: needkw = 32; par.type = para_UnnumberedChapter; break;
/* For \b and \n the keyword is optional */
case c_b: needkw = 4; par.type = para_Bullet; break;
+ case c_dt: needkw = 4; par.type = para_DescribedThing; break;
+ case c_dd: needkw = 4; par.type = para_Description; break;
case c_n: needkw = 4; par.type = para_NumberedList; break;
case c_cfg: needkw = 8; par.type = para_Config;
start_cmd = c_cfg; break;
case c_versionid: needkw = 32; par.type = para_VersionID; break;
}
+ if (par.type == para_Chapter ||
+ par.type == para_Heading ||
+ par.type == para_Subsect ||
+ par.type == para_Appendix ||
+ par.type == para_UnnumberedChapter) {
+ struct crossparaitem *sitem = stk_top(crossparastk);
+ if (sitem && sitem->seen_lcont) {
+ error(err_sectmarkerinlcont, &t.pos);
+ }
+ }
+
if (needkw > 0) {
rdstring rs = { 0, 0, NULL };
int nkeys = 0;
}
if (t.type == tok_cmd)
already = TRUE;/* inhibit get_token at top of loop */
+ prev_para_type = par.type;
addpara(par, ret);
continue; /* next paragraph */
}
break;
case tok_rbrace:
sitem = stk_pop(parsestk);
- if (!sitem)
- error(err_unexbrace, &t.pos);
- else {
+ if (!sitem) {
+ /*
+ * This closing brace could have been an
+ * indication that the cross-paragraph stack
+ * wants popping. Accordingly, we treat it here
+ * as an indication that the paragraph is over.
+ */
+ already = TRUE;
+ goto finished_para;
+ } else {
if (sitem->type & stack_ualt) {
whptr = sitem->whptr;
idximplicit = sitem->idximplicit;
dtor(t), t = get_token(in);
seenwhite = iswhite;
}
+ finished_para:
/* Check the stack is empty */
- if (NULL != (sitem = stk_pop(parsestk))) {
- do {
+ if (stk_top(parsestk)) {
+ while ((sitem = stk_pop(parsestk)))
sfree(sitem);
- sitem = stk_pop(parsestk);
- } while (sitem);
error(err_missingrbrace, &t.pos);
}
stk_free(parsestk);
+ prev_para_type = par.type;
addpara(par, ret);
+ if (t.type == tok_eof)
+ already = TRUE;
+ }
+
+ if (stk_top(crossparastk)) {
+ void *p;
+
+ error(err_missingrbrace2, &t.pos);
+ while ((p = stk_pop(crossparastk)))
+ sfree(p);
}
/*
*/
dtor(t);
macrocleanup(macros);
+
+ stk_free(crossparastk);
}
paragraph *read_input(input *in, indexdata *idx) {
\define{coopt} co\u00F6{-o}pt
-\versionid $Id: test.but,v 1.19 2002/08/12 11:24:28 simon Exp $
+\versionid $Id: test.but,v 1.20 2004/03/23 20:10:23 simon Exp $
\C{ch\\ap} First chapter title; for similar wrapping reasons this
chapter title will be ludicrously long. I wonder how much more
\cw{This} is weak code. And \k{head} contains some other stuff.
\K{subhead} does too.
+To test the man page back end:
+
+.Directive
+
+'Directive
+
+\\Sping\\Spong\\Spoing
+
\H{head} First section title (very long again, no prizes for
guessing the reason why this time, and here's yet more text to pad
it out to three lines of output)
\b Aah.
+\lcont{
+
+This bulletted list contains a list continuation. This is an
+additional paragraph, or more than one, indented at the same level
+as the list items, and able to contain nested sublists and other
+features. For example, here's a code paragraph:
+
+\c spingle:~$ whoami
+\c spoggler
+
+And here's a sublist. Numbered, just for variety.
+
+\n One.
+
+\lcont{
+
+\n 1a.
+
+\n 1b.
+
+\n 1c.
+
+\lcont{This is an even sillier one: a continuation of a list item in
+a continuation of a list item in a continuation of a list item!}
+
+}
+
+\n Two.
+
+\n Threeeee!
+
+}
+
\b Eek.
This is a horizontal rule:
\n Eek. \q{Aah} is point \k{keyword}.
+This is a description list:
+
+\dt FISH
+
+\dd A piscine creature, often to be found swimming aimlessly around
+in the sea eating things and not contributing to the global economy.
+
+\lcont{
+
+Here's another of those funky list continuation things, just to keep
+Halibut on its toes.
+
+}
+
+\dt BADGER
+
+\dd A non-piscine creature, often to be found snuffling around on
+land, not contributing to the global economy, and not even swimming
+to make up for it. I don't know. These mammals. Pa-thetic.
+
+\dt "SAUSAGE SALESMAN"
+
+\dd An exemplary contributor to the global economy. Unless he's CMOT
+Dibbler.
+
A-paragraph-full-of-hyphens-to-test-the-idea-that-word-wrapping-can-happen-somewhere-in-all-this-hyphenatory-nonsense.
A\-paragraph\-full\-of\-nonbreaking\-hyphens\-to\-test\-the\-idea\-that\-word\-wrapping\-misses\-them.
text_backend(sourceform, keywords, idx);
xhtml_backend(sourceform, keywords, idx);
whlp_backend(sourceform, keywords, idx);
+ man_backend(sourceform, keywords, idx);
free_para_list(sourceform);
free_keywords(keywords);
return NULL;
}
+void *stk_top(stack s) {
+ if (s->sp > 0)
+ return s->data[s->sp-1];
+ else
+ return NULL;
+}
+
/*
* Small routines to amalgamate a string from an input source.
*/