diff options
-rw-r--r-- | main.c | 4 | ||||
-rw-r--r-- | mandoc.h | 4 | ||||
-rw-r--r-- | regress/roff/if/multiline-free0.in | 10 | ||||
-rw-r--r-- | regress/roff/if/multiline-free1.in | 11 | ||||
-rw-r--r-- | regress/roff/ig/end0.in | 12 | ||||
-rw-r--r-- | regress/roff/ig/end1.in | 12 | ||||
-rw-r--r-- | regress/roff/ig/redef0.in | 13 | ||||
-rw-r--r-- | regress/roff/ig/simple0.in | 12 | ||||
-rw-r--r-- | regress/roff/ig/simple1.in | 12 | ||||
-rw-r--r-- | regress/roff/ig/simple2.in | 15 | ||||
-rw-r--r-- | roff.7 | 70 | ||||
-rw-r--r-- | roff.c | 145 |
12 files changed, 290 insertions, 30 deletions
@@ -798,8 +798,8 @@ mwarn(void *arg, int line, int col, const char *msg) static const char * const mandocerrs[MANDOCERR_MAX] = { "ok", "multi-line scope open on exit", - "request for scope closure when no matching scope is open", - "macro requires line argument(s)", + "request for scope closure when no matching scope is open: ignored", + "macro requires line argument(s): ignored", "line arguments will be lost", "memory exhausted" }; @@ -22,10 +22,10 @@ __BEGIN_DECLS enum mandocerr { MANDOCERR_OK, MANDOCERR_SCOPEEXIT, /* scope open on exit */ - MANDOCERR_NOSCOPE, /* request scope close w/none open */ - MANDOCERR_NOARGS, /* macro requires argument(s) */ #define MANDOCERR_WARNING MANDOCERR_SCOPEEXIT + MANDOCERR_NOSCOPE, /* request scope close w/none open */ + MANDOCERR_NOARGS, /* macro requires argument(s) */ MANDOCERR_ARGSLOST, /* line arguments will be lost */ #define MANDOCERR_ERROR MANDOCERR_ARGSLOST diff --git a/regress/roff/if/multiline-free0.in b/regress/roff/if/multiline-free0.in new file mode 100644 index 00000000..f9fb0fd9 --- /dev/null +++ b/regress/roff/if/multiline-free0.in @@ -0,0 +1,10 @@ +.Dd $Mdocdate$ +.Dt FOO 1 +.Os +.Sh NAME +.Nm foo +.Nd bar +.Sh DESCRIPTION +.if t \{\ +there \} dude +fdsa diff --git a/regress/roff/if/multiline-free1.in b/regress/roff/if/multiline-free1.in new file mode 100644 index 00000000..ac147435 --- /dev/null +++ b/regress/roff/if/multiline-free1.in @@ -0,0 +1,11 @@ +.Dd $Mdocdate$ +.Dt FOO 1 +.Os +.Sh NAME +.Nm foo +.Nd bar +.Sh DESCRIPTION +.if t \{\ +there \\} dude +.\} +fdsa diff --git a/regress/roff/ig/end0.in b/regress/roff/ig/end0.in new file mode 100644 index 00000000..ad35726d --- /dev/null +++ b/regress/roff/ig/end0.in @@ -0,0 +1,12 @@ +.Dd $Mdocdate$ +.Dt FOO 1 +.Os +.Sh NAME +.Nm foo +.Nd bar +.Sh DESCRIPTION +a +.ig foo +asdf +.foo +b diff --git a/regress/roff/ig/end1.in b/regress/roff/ig/end1.in new file mode 100644 index 00000000..c1d075af --- /dev/null +++ b/regress/roff/ig/end1.in @@ -0,0 +1,12 @@ +.Dd $Mdocdate$ +.Dt FOO 1 +.Os +.Sh NAME +.Nm foo +.Nd bar +.Sh DESCRIPTION +a +.ig foo +asdf +.foo +b diff --git a/regress/roff/ig/redef0.in b/regress/roff/ig/redef0.in new file mode 100644 index 00000000..4d5a7e76 --- /dev/null +++ b/regress/roff/ig/redef0.in @@ -0,0 +1,13 @@ +.Dd $Mdocdate$ +.Dt FOO 1 +.Os +.Sh NAME +.Nm foo +.Nd bar +.Sh DESCRIPTION +a +.ig if +asdf +.if t \ +b +c diff --git a/regress/roff/ig/simple0.in b/regress/roff/ig/simple0.in new file mode 100644 index 00000000..d693ce77 --- /dev/null +++ b/regress/roff/ig/simple0.in @@ -0,0 +1,12 @@ +.Dd $Mdocdate$ +.Dt FOO 1 +.Os +.Sh NAME +.Nm foo +.Nd bar +.Sh DESCRIPTION +123 +.ig +hello +.. +12124 diff --git a/regress/roff/ig/simple1.in b/regress/roff/ig/simple1.in new file mode 100644 index 00000000..d693ce77 --- /dev/null +++ b/regress/roff/ig/simple1.in @@ -0,0 +1,12 @@ +.Dd $Mdocdate$ +.Dt FOO 1 +.Os +.Sh NAME +.Nm foo +.Nd bar +.Sh DESCRIPTION +123 +.ig +hello +.. +12124 diff --git a/regress/roff/ig/simple2.in b/regress/roff/ig/simple2.in new file mode 100644 index 00000000..4e82458c --- /dev/null +++ b/regress/roff/ig/simple2.in @@ -0,0 +1,15 @@ +.Dd $Mdocdate$ +.Dt FOO 1 +.Os +.Sh NAME +.Nm foo +.Nd bar +.Sh DESCRIPTION +123 +.ig +hello +.if t \{\ +hello +.\} +.. +12124 @@ -76,6 +76,10 @@ BODY... .Ed .Bd -literal -offset indent -compact \&.if COND \e{ BODY +BODY... \e} +.Ed +.Bd -literal -offset indent -compact +\&.if COND \e{ BODY BODY... \&.\e} .Ed @@ -112,15 +116,71 @@ The scope of a conditional is always parsed, but only executed if the conditional evaluates to true. .Pp Note that text subsequent a +.Sq \&.\e} +macro is discarded. +Furthermore, if an explicit closing sequence .Sq \e} -is discarded. +is specified in a free-form line, the entire line is accepted within the +scope of the prior macro, not only the text preceding the close. .Ss \&ig -Ignore input until a -.Sq \.\. +Ignore input. +Accepts the following syntax: +.Pp +.Bd -literal -offset indent -compact +\&.ig +BODY... +\&.. +.Ed +.Bd -literal -offset indent -compact +\&.ig END +BODY... +\&.END +.Ed +.Pp +In the first case, input is ignored until a +.Sq \&.. macro is encountered on its own line. -Note that text subsequent the -.Sq \.\. +In the second case, input is ignored until a +.Sq \&.END +is encountered. +Text subsequent the +.Sq \&.END +or +.Sq \&.. is discarded. +.Pp +Do not use the escape +.Sq \e +anywhere in the definition of END. +It causes very strange behaviour. +Furthermore, if you redefine a +.Nm +macro, such as +.Pp +.D1 \&.ig if +.Pp +the subsequent invocation of +.Sx \&if +will first signify the end of comment, then be invoked as a macro. +This behaviour really shouldn't be counted upon. +.Sh COMPATIBILITY +This section documents compatibility between mandoc and other other +troff implementations, at this time limited to GNU troff +.Pq Qq groff . +The term +.Qq historic groff +refers to groff versions before the +.Pa doc.tmac +file re-write +.Pq somewhere between 1.15 and 1.19 . +.Pp +.Bl -dash -compact +.It +Historic groff did not accept white-space buffering the custom END tag +for the +.Sx \&ig +macro. +.El .Sh AUTHORS The .Nm @@ -81,24 +81,21 @@ typedef enum rofferr (*roffproc)(ROFF_ARGS); struct roffmac { const char *name; /* macro name */ roffproc proc; + roffproc text; }; static enum rofferr roff_if(ROFF_ARGS); +static enum rofferr roff_if_text(ROFF_ARGS); static enum rofferr roff_ig(ROFF_ARGS); +static enum rofferr roff_ig_text(ROFF_ARGS); static enum rofferr roff_cblock(ROFF_ARGS); static enum rofferr roff_ccond(ROFF_ARGS); const struct roffmac roffs[ROFF_MAX] = { - { "if", roff_if }, - { "ig", roff_ig }, - { ".", roff_cblock }, - { "\\}", roff_ccond }, -#if 0 - { "am", roff_sub_ig, roff_new_ig }, - { "ami", roff_sub_ig, roff_new_ig }, - { "de", roff_sub_ig, roff_new_ig }, - { "dei", roff_sub_ig, roff_new_ig }, -#endif + { "if", roff_if, roff_if_text }, + { "ig", roff_ig, roff_ig_text }, + { ".", roff_cblock, NULL }, + { "\\}", roff_ccond, NULL }, }; static void roff_free1(struct roff *); @@ -218,28 +215,72 @@ roff_parseln(struct roff *r, int ln, char **bufp, size_t *szp, int pos, int *offs) { enum rofft t; - int ppos; + int ppos, i, j, wtf; if (r->last && ! ROFF_CTL((*bufp)[pos])) { - if (ROFF_ig == r->last->tok) - return(ROFF_IGN); - roffnode_cleanscope(r); - /* FIXME: this assumes we're discarding! */ - return(ROFF_IGN); + /* + * If a scope is open and we're not a macro, pass it + * through our text detector and continue as quickly as + * possible. + */ + t = r->last->tok; + assert(roffs[t].text); + return((*roffs[t].text) + (r, t, bufp, szp, ln, pos, pos, offs)); } else if ( ! ROFF_CTL((*bufp)[pos])) + /* + * Don't do anything if we're free-form text. + */ return(ROFF_CONT); - /* There's nothing on the stack: make us anew. */ + /* A macro-ish line with a possibly-open macro context. */ + + wtf = 0; + + if (r->last && r->last->end) { + /* + * We have a scope open that has a custom end-macro + * handler. Try to match it against the input. + */ + i = pos + 1; + while (' ' == (*bufp)[i] || '\t' == (*bufp)[i]) + i++; + + for (j = 0; r->last->end[j]; j++, i++) + if ((*bufp)[i] != r->last->end[j]) + break; + + if ('\0' == r->last->end[j] && + ('\0' == (*bufp)[i] || + ' ' == (*bufp)[i] || + '\t' == (*bufp)[i])) { + roffnode_pop(r); + roffnode_cleanscope(r); + wtf = 1; + } + } ppos = pos; if (ROFF_MAX == (t = roff_parse(*bufp, &pos))) { - if (r->last && ROFF_ig == r->last->tok) + /* + * This is some of groff's stranger behaviours. If we + * encountered a custom end-scope tag and that tag also + * happens to be a "real" macro, then we need to try + * interpreting it again as a real macro. If it's not, + * then return ignore. Else continue. + */ + if (wtf) return(ROFF_IGN); - return(ROFF_CONT); + else if (NULL == r->last) + return(ROFF_CONT); + + /* FIXME: this assumes that we ignore!? */ + return(ROFF_IGN); } assert(roffs[t].proc); - return((*roffs[t].proc)(r, t, bufp, szp, ln, ppos, pos, offs)); + return((*roffs[t].proc) + (r, t, bufp, szp, ln, ppos, pos, offs)); } @@ -375,11 +416,42 @@ roff_ccond(ROFF_ARGS) static enum rofferr roff_ig(ROFF_ARGS) { + int sv; + size_t sz; if ( ! roffnode_push(r, tok, ln, ppos)) return(ROFF_ERR); - ROFF_MDEBUG(r, "opening ignore block"); + if ('\0' == (*bufp)[pos]) { + ROFF_MDEBUG(r, "opening ignore block"); + return(ROFF_IGN); + } + + sv = pos; + while ((*bufp)[pos] && ' ' != (*bufp)[pos] && + '\t' != (*bufp)[pos]) + pos++; + + /* + * Note: groff does NOT like escape characters in the input. + * Instead of detecting this, we're just going to let it fly and + * to hell with it. + */ + + assert(pos > sv); + sz = (size_t)(pos - sv); + + r->last->end = malloc(sz + 1); + + if (NULL == r->last->end) { + (*r->msg)(MANDOCERR_MEM, r->data, ln, pos, NULL); + return(ROFF_ERR); + } + + memcpy(r->last->end, *bufp + sv, sz); + r->last->end[(int)sz] = '\0'; + + ROFF_MDEBUG(r, "opening explicit ignore block"); if ((*bufp)[pos]) if ( ! (*r->msg)(MANDOCERR_ARGSLOST, r->data, ln, pos, NULL)) @@ -391,6 +463,37 @@ roff_ig(ROFF_ARGS) /* ARGSUSED */ static enum rofferr +roff_ig_text(ROFF_ARGS) +{ + + return(ROFF_IGN); +} + + +/* ARGSUSED */ +static enum rofferr +roff_if_text(ROFF_ARGS) +{ + char *ep, *st; + + st = &(*bufp)[pos]; + if (NULL == (ep = strstr(st, "\\}"))) { + roffnode_cleanscope(r); + return(ROFF_IGN); + } + + if (ep > st && '\\' != *(ep - 1)) { + ROFF_MDEBUG(r, "closing explicit scope (in-line)"); + roffnode_pop(r); + } + + roffnode_cleanscope(r); + return(ROFF_IGN); +} + + +/* ARGSUSED */ +static enum rofferr roff_if(ROFF_ARGS) { int sv; |