Rudimentary implementation of the roff(7) .while request.
authorschwarze <schwarze@openbsd.org>
Fri, 24 Aug 2018 22:56:37 +0000 (22:56 +0000)
committerschwarze <schwarze@openbsd.org>
Fri, 24 Aug 2018 22:56:37 +0000 (22:56 +0000)
Needed for example by groff_hdtbl(7).

There are two limitations:
It does not support nested .while requests yet,
and each .while loop must start and end in the same scope.

The roff_parseln() return codes are now more flexible
and allow OR'ing options.

21 files changed:
regress/usr.bin/mandoc/roff/Makefile
regress/usr.bin/mandoc/roff/while/Makefile [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/badargs.in [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/badargs.out_ascii [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/badargs.out_lint [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/basic.in [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/basic.out_ascii [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/into.in [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/into.out_ascii [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/into.out_lint [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/nesting.in [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/nesting.out_ascii [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/nesting.out_lint [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/outof.in [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/outof.out_ascii [new file with mode: 0644]
regress/usr.bin/mandoc/roff/while/outof.out_lint [new file with mode: 0644]
share/man/man7/roff.7
usr.bin/mandoc/libmandoc.h
usr.bin/mandoc/mandoc.h
usr.bin/mandoc/read.c
usr.bin/mandoc/roff.c

index 7c6f5e1..f338f83 100644 (file)
@@ -1,7 +1,8 @@
-# $OpenBSD: Makefile,v 1.25 2018/08/23 14:16:12 schwarze Exp $
+# $OpenBSD: Makefile,v 1.26 2018/08/24 22:56:37 schwarze Exp $
 
 SUBDIR  = args cond esc scale string
-SUBDIR += br cc de ds ft ig it ll na nr po ps return rm rn shift sp ta ti tr
+SUBDIR += br cc de ds ft ig it ll na nr po ps
+SUBDIR += return rm rn shift sp ta ti tr while
 
 .include "../Makefile.sub"
 .include <bsd.subdir.mk>
diff --git a/regress/usr.bin/mandoc/roff/while/Makefile b/regress/usr.bin/mandoc/roff/while/Makefile
new file mode 100644 (file)
index 0000000..ed97047
--- /dev/null
@@ -0,0 +1,13 @@
+# $OpenBSD: Makefile,v 1.1 2018/08/24 22:56:37 schwarze Exp $
+
+REGRESS_TARGETS        = basic badargs into nesting outof
+LINT_TARGETS   = badargs into nesting outof
+
+# mandoc defects:
+#  - if a while loop extends into a scope, mandoc may close it there
+#  - mandoc does not support nested .while loops
+#  - mandoc does not support .while loops extending out of the current scope
+
+SKIP_GROFF     = into nesting outof
+
+.include <bsd.regress.mk>
diff --git a/regress/usr.bin/mandoc/roff/while/badargs.in b/regress/usr.bin/mandoc/roff/while/badargs.in
new file mode 100644 (file)
index 0000000..bbbd7cd
--- /dev/null
@@ -0,0 +1,14 @@
+.\" $OpenBSD: badargs.in,v 1.1 2018/08/24 22:56:37 schwarze Exp $
+.Dd $Mdocdate: August 24 2018 $
+.Dt WHILE-BADARGS 1
+.Os
+.Sh NAME
+.Nm while-badargs
+.Nd dubious arguments for the while request
+.Sh DESCRIPTION
+while does not support next line scope:
+.nr cnt 2 1
+.while \n-[cnt]
+\n[cnt]
+.Pp
+final text
diff --git a/regress/usr.bin/mandoc/roff/while/badargs.out_ascii b/regress/usr.bin/mandoc/roff/while/badargs.out_ascii
new file mode 100644 (file)
index 0000000..8878d64
--- /dev/null
@@ -0,0 +1,13 @@
+WHILE-BADARGS(1)            General Commands Manual           WHILE-BADARGS(1)
+
+N\bNA\bAM\bME\bE
+     w\bwh\bhi\bil\ble\be-\b-b\bba\bad\bda\bar\brg\bgs\bs - dubious arguments for the while request
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     while does not support next line scope:
+
+     0
+
+     final text
+
+OpenBSD                         August 25, 2018                        OpenBSD
diff --git a/regress/usr.bin/mandoc/roff/while/badargs.out_lint b/regress/usr.bin/mandoc/roff/while/badargs.out_lint
new file mode 100644 (file)
index 0000000..293ecc0
--- /dev/null
@@ -0,0 +1,3 @@
+mandoc: badargs.in:11:2: WARNING: conditional request controls empty scope: while
+mandoc: badargs.in:11:9: WARNING: blank line in fill mode, using .sp
+mandoc: badargs.in:11:2: WARNING: conditional request controls empty scope: while
diff --git a/regress/usr.bin/mandoc/roff/while/basic.in b/regress/usr.bin/mandoc/roff/while/basic.in
new file mode 100644 (file)
index 0000000..6bd09e3
--- /dev/null
@@ -0,0 +1,30 @@
+.\" $OpenBSD: basic.in,v 1.1 2018/08/24 22:56:37 schwarze Exp $
+.Dd $Mdocdate: August 24 2018 $
+.Dt WHILE-BASIC 1
+.Os
+.Sh NAME
+.Nm while-basic
+.Nd the while request
+.Sh DESCRIPTION
+Loop with single-line scope:
+.nr cnt 11 1
+.de mym
+\\n-[cnt]
+..
+.while \n[cnt] .mym
+.Pp
+Loop with multi-line scope, text line closure:
+.nr cnt 11
+.while \n[cnt] \{\
+.nr cnt -1
+\n[cnt]\},
+boom.
+.Pp
+Loop with multi-line scope, macro line closure:
+.nr cnt 11
+.while \n[cnt] \{\
+.nr cnt -1
+\n[cnt]
+.\}
+.Pp
+final text
diff --git a/regress/usr.bin/mandoc/roff/while/basic.out_ascii b/regress/usr.bin/mandoc/roff/while/basic.out_ascii
new file mode 100644 (file)
index 0000000..b97abb4
--- /dev/null
@@ -0,0 +1,16 @@
+WHILE-BASIC(1)              General Commands Manual             WHILE-BASIC(1)
+
+N\bNA\bAM\bME\bE
+     w\bwh\bhi\bil\ble\be-\b-b\bba\bas\bsi\bic\bc - the while request
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     Loop with single-line scope: 10 9 8 7 6 5 4 3 2 1 0
+
+     Loop with multi-line scope, text line closure: 10, 9, 8, 7, 6, 5, 4, 3,
+     2, 1, 0, boom.
+
+     Loop with multi-line scope, macro line closure: 10 9 8 7 6 5 4 3 2 1 0
+
+     final text
+
+OpenBSD                         August 25, 2018                        OpenBSD
diff --git a/regress/usr.bin/mandoc/roff/while/into.in b/regress/usr.bin/mandoc/roff/while/into.in
new file mode 100644 (file)
index 0000000..5a840ab
--- /dev/null
@@ -0,0 +1,20 @@
+.\" $OpenBSD: into.in,v 1.1 2018/08/24 22:56:37 schwarze Exp $
+.Dd $Mdocdate: August 24 2018 $
+.Dt WHILE-INTO 1
+.Os
+.Sh NAME
+.Nm while-into
+.Nd while request extending into a macro
+.Sh DESCRIPTION
+.nr cnt 10
+.de closeloop
+.nr cnt -1
+.\}
+..
+initial text
+.while \n[cnt] \{\
+\n[cnt]
+.closeloop
+after macro
+.\}
+final text
diff --git a/regress/usr.bin/mandoc/roff/while/into.out_ascii b/regress/usr.bin/mandoc/roff/while/into.out_ascii
new file mode 100644 (file)
index 0000000..cf8e561
--- /dev/null
@@ -0,0 +1,9 @@
+WHILE-INTO(1)               General Commands Manual              WHILE-INTO(1)
+
+N\bNA\bAM\bME\bE
+     w\bwh\bhi\bil\ble\be-\b-i\bin\bnt\bto\bo - while request extending into a macro
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     initial text 10 after macro final text
+
+OpenBSD                         August 25, 2018                        OpenBSD
diff --git a/regress/usr.bin/mandoc/roff/while/into.out_lint b/regress/usr.bin/mandoc/roff/while/into.out_lint
new file mode 100644 (file)
index 0000000..04a7522
--- /dev/null
@@ -0,0 +1,2 @@
+mandoc: into.in:17:5: UNSUPP: end of .while loop in inner scope
+mandoc: into.in:20:1: UNSUPP: end of scope with open .while loop
diff --git a/regress/usr.bin/mandoc/roff/while/nesting.in b/regress/usr.bin/mandoc/roff/while/nesting.in
new file mode 100644 (file)
index 0000000..930b8b7
--- /dev/null
@@ -0,0 +1,19 @@
+.\" $OpenBSD: nesting.in,v 1.1 2018/08/24 22:56:37 schwarze Exp $
+.Dd $Mdocdate: August 24 2018 $
+.Dt WHILE-NESTING 1
+.Os
+.Sh NAME
+.Nm while-nesting
+.Nd nested while requests
+.Sh DESCRIPTION
+initial text
+.nr c1 3
+.while \n(c1 \{\
+.  nr c2 3
+.  while \n(c2 \{\
+.    nop \n(c1\n(c2
+.    nr c2 -1
+.  \}
+.  nr c1 -1
+.\}
+final text
diff --git a/regress/usr.bin/mandoc/roff/while/nesting.out_ascii b/regress/usr.bin/mandoc/roff/while/nesting.out_ascii
new file mode 100644 (file)
index 0000000..8aae57d
--- /dev/null
@@ -0,0 +1,9 @@
+WHILE-NESTING(1)            General Commands Manual           WHILE-NESTING(1)
+
+N\bNA\bAM\bME\bE
+     w\bwh\bhi\bil\ble\be-\b-n\bne\bes\bst\bti\bin\bng\bg - nested while requests
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     initial text 33 32 31 final text
+
+OpenBSD                         August 25, 2018                        OpenBSD
diff --git a/regress/usr.bin/mandoc/roff/while/nesting.out_lint b/regress/usr.bin/mandoc/roff/while/nesting.out_lint
new file mode 100644 (file)
index 0000000..7ea34ba
--- /dev/null
@@ -0,0 +1,2 @@
+mandoc: nesting.in:14:37: UNSUPP: nested .while loops
+mandoc: nesting.in:18:4: UNSUPP: cannot continue this .while loop
diff --git a/regress/usr.bin/mandoc/roff/while/outof.in b/regress/usr.bin/mandoc/roff/while/outof.in
new file mode 100644 (file)
index 0000000..4da3453
--- /dev/null
@@ -0,0 +1,18 @@
+.\" $OpenBSD: outof.in,v 1.1 2018/08/24 22:56:37 schwarze Exp $
+.Dd $Mdocdate: August 24 2018 $
+.Dt WHILE-OUTOF 1
+.Os
+.Sh NAME
+.Nm while-outof
+.Nd while request starting in a macro
+.Sh DESCRIPTION
+.nr cnt 10
+.de mym
+.  while \\n[cnt] \{\
+.    nop \\n[cnt]
+..
+initial text
+.mym
+.  nr cnt -1
+.\}
+final text
diff --git a/regress/usr.bin/mandoc/roff/while/outof.out_ascii b/regress/usr.bin/mandoc/roff/while/outof.out_ascii
new file mode 100644 (file)
index 0000000..4a3dbe7
--- /dev/null
@@ -0,0 +1,9 @@
+WHILE-OUTOF(1)              General Commands Manual             WHILE-OUTOF(1)
+
+N\bNA\bAM\bME\bE
+     w\bwh\bhi\bil\ble\be-\b-o\bou\but\bto\bof\bf - while request starting in a macro
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     initial text 10 final text
+
+OpenBSD                         August 25, 2018                        OpenBSD
diff --git a/regress/usr.bin/mandoc/roff/while/outof.out_lint b/regress/usr.bin/mandoc/roff/while/outof.out_lint
new file mode 100644 (file)
index 0000000..6ab0c9f
--- /dev/null
@@ -0,0 +1,2 @@
+mandoc: outof.in:15:1: UNSUPP: end of scope with open .while loop
+mandoc: outof.in:17:4: UNSUPP: cannot continue this .while loop
index 2914ce4..e1bf722 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: roff.7,v 1.83 2018/08/23 14:16:11 schwarze Exp $
+.\"    $OpenBSD: roff.7,v 1.84 2018/08/24 22:56:37 schwarze Exp $
 .\"
 .\" Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
 .\" Copyright (c) 2010-2018 Ingo Schwarze <schwarze@openbsd.org>
@@ -15,7 +15,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: August 23 2018 $
+.Dd $Mdocdate: August 24 2018 $
 .Dt ROFF 7
 .Os
 .Sh NAME
@@ -1200,7 +1200,7 @@ While evaluating the
 the unit suffixes described below
 .Sx Scaling Widths
 are ignored.
-.It Ic \&it Ar expression macro
+.It Ic \&itc Ar expression macro
 Set an input line trap, not counting lines ending with \ec.
 Currently unsupported.
 .It Ic \&IX Ar class keystring
@@ -1713,8 +1713,12 @@ This is a Heirloom extension and currently ignored.
 Set a page location trap.
 Currently unsupported.
 .It Ic \&while Ar condition body
-Repeated execution while a condition is true.
-Currently unsupported.
+Repeated execution while a
+.Ar condition
+is true, with syntax similar to
+.Ic \&if .
+Currently implemented with two restrictions: cannot nest,
+and each loop must start and end in the same scope.
 .It Ic \&write Oo \(dq Oc Ns Ar string
 Write to an open file.
 Ignored because insecure.
@@ -2147,10 +2151,6 @@ macro control character does not suppress output line breaks.
 .It
 Diversions are not implemented,
 and support for traps is very incomplete.
-.It
-While recursion is supported,
-.Sx \&while
-loops are not.
 .El
 .Pp
 The special semantics of the
index 336614c..b5e492e 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: libmandoc.h,v 1.58 2018/08/23 19:32:03 schwarze Exp $ */
+/*     $OpenBSD: libmandoc.h,v 1.59 2018/08/24 22:56:37 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2013,2014,2015,2017,2018 Ingo Schwarze <schwarze@openbsd.org>
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-enum   rofferr {
-       ROFF_CONT, /* continue processing line */
-       ROFF_RERUN, /* re-run roff interpreter with offset */
-       ROFF_APPEND, /* re-run main parser, appending next line */
-       ROFF_REPARSE, /* re-run main parser on the result */
-       ROFF_USERCALL, /* dto., calling a user-defined macro */
-       ROFF_USERRET, /* abort parsing of user-defined macro */
-       ROFF_SO, /* include another file */
-       ROFF_IGN, /* ignore current line */
-};
+/*
+ * Return codes passed from the roff parser to the main parser.
+ */
+
+/* Main instruction: what to do with the returned line. */
+#define        ROFF_IGN        0x000   /* Don't do anything with it. */
+#define        ROFF_CONT       0x001   /* Give it to the high-level parser. */
+#define        ROFF_RERUN      0x002   /* Re-run the roff parser with an offset. */
+#define        ROFF_REPARSE    0x004   /* Recursively run the main parser on it. */
+#define        ROFF_SO         0x008   /* Include the named file. */
+#define        ROFF_MASK       0x00f   /* Only one of these bits should be set. */
+
+/* Options for further parsing, to be OR'ed with the above. */
+#define        ROFF_APPEND     0x010   /* Append the next line to this one. */
+#define        ROFF_USERCALL   0x020   /* Start execution of a new macro. */
+#define        ROFF_USERRET    0x040   /* Abort execution of the current macro. */
+#define        ROFF_WHILE      0x100   /* Start a new .while loop. */
+#define        ROFF_LOOPCONT   0x200   /* Iterate the current .while loop. */
+#define        ROFF_LOOPEXIT   0x400   /* Exit the current .while loop. */
+#define        ROFF_LOOPMASK   0xf00
+
 
 struct buf {
        char            *buf;
@@ -66,7 +77,7 @@ void           roff_man_free(struct roff_man *);
 struct roff_man        *roff_man_alloc(struct roff *, struct mparse *,
                        const char *, int);
 void            roff_man_reset(struct roff_man *);
-enum rofferr    roff_parseln(struct roff *, int, struct buf *, int *);
+int             roff_parseln(struct roff *, int, struct buf *, int *);
 void            roff_userret(struct roff *);
 void            roff_endparse(struct roff *);
 void            roff_setreg(struct roff *, const char *, int, char sign);
index fad9c87..6b39fc0 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: mandoc.h,v 1.193 2018/08/23 19:32:03 schwarze Exp $ */
+/*     $OpenBSD: mandoc.h,v 1.194 2018/08/24 22:56:37 schwarze Exp $ */
 /*
  * Copyright (c) 2010, 2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010-2018 Ingo Schwarze <schwarze@openbsd.org>
@@ -228,6 +228,10 @@ enum       mandocerr {
        MANDOCERR_TOOLARGE, /* input too large */
        MANDOCERR_CHAR_UNSUPP, /* unsupported control character: number */
        MANDOCERR_REQ_UNSUPP, /* unsupported roff request: request */
+       MANDOCERR_WHILE_NEST, /* nested .while loops */
+       MANDOCERR_WHILE_OUTOF, /* end of scope with open .while loop */
+       MANDOCERR_WHILE_INTO, /* end of .while loop in inner scope */
+       MANDOCERR_WHILE_FAIL, /* cannot continue this .while loop */
        MANDOCERR_TBLOPT_EQN, /* eqn delim option in tbl: arg */
        MANDOCERR_TBLLAYOUT_MOD, /* unsupported tbl layout modifier: m */
        MANDOCERR_TBLMACRO, /* ignoring macro in table: macro */
index 0041570..2ab9dae 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: read.c,v 1.170 2018/08/23 19:32:03 schwarze Exp $ */
+/*     $OpenBSD: read.c,v 1.171 2018/08/24 22:56:37 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010-2018 Ingo Schwarze <schwarze@openbsd.org>
@@ -47,6 +47,7 @@ struct        mparse {
        const char       *file; /* filename of current input file */
        struct buf       *primary; /* buffer currently being parsed */
        struct buf       *secondary; /* copy of top level input */
+       struct buf       *loop; /* open .while request line */
        const char       *os_s; /* default operating system */
        mandocmsg         mmsg; /* warning/error message handler */
        enum mandoclevel  file_status; /* status of current parse */
@@ -61,7 +62,7 @@ struct        mparse {
 static void      choose_parser(struct mparse *);
 static void      free_buf_list(struct buf *);
 static void      resize_buf(struct buf *, size_t);
-static enum rofferr mparse_buf_r(struct mparse *, struct buf, size_t, int);
+static int       mparse_buf_r(struct mparse *, struct buf, size_t, int);
 static int       read_whole_file(struct mparse *, const char *, int,
                                struct buf *, int *);
 static void      mparse_end(struct mparse *);
@@ -264,6 +265,10 @@ static     const char * const      mandocerrs[MANDOCERR_MAX] = {
        "input too large",
        "unsupported control character",
        "unsupported roff request",
+       "nested .while loops",
+       "end of scope with open .while loop",
+       "end of .while loop in inner scope",
+       "cannot continue this .while loop",
        "eqn delim option in tbl",
        "unsupported tbl layout modifier",
        "ignoring macro in table",
@@ -354,32 +359,31 @@ choose_parser(struct mparse *curp)
  * macros, inline equations, and input line traps)
  * and indirectly (for .so file inclusion).
  */
-static enum rofferr
+static int
 mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start)
 {
        struct buf       ln;
-       struct buf      *firstln, *lastln, *thisln;
+       struct buf      *firstln, *lastln, *thisln, *loop;
        const char      *save_file;
        char            *cp;
        size_t           pos; /* byte number in the ln buffer */
-       enum rofferr     line_result, result;
+       int              line_result, result;
        int              of;
        int              lnn; /* line number in the real file */
        int              fd;
+       int              inloop; /* Saw .while on this level. */
        unsigned char    c;
 
        ln.sz = 256;
        ln.buf = mandoc_malloc(ln.sz);
        ln.next = NULL;
-       firstln = NULL;
+       firstln = loop = NULL;
        lnn = curp->line;
        pos = 0;
+       inloop = 0;
        result = ROFF_CONT;
 
-       while (i < blk.sz) {
-               if (0 == pos && '\0' == blk.buf[i])
-                       break;
-
+       while (i < blk.sz && (blk.buf[i] != '\0' || pos != 0)) {
                if (start) {
                        curp->line = lnn;
                        curp->reparse_count = 0;
@@ -488,41 +492,95 @@ mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start)
 rerun:
                line_result = roff_parseln(curp->roff, curp->line, &ln, &of);
 
-               switch (line_result) {
+               /* Process options. */
+
+               if (line_result & ROFF_APPEND)
+                       assert(line_result == (ROFF_IGN | ROFF_APPEND));
+
+               if (line_result & ROFF_USERCALL)
+                       assert((line_result & ROFF_MASK) == ROFF_REPARSE);
+
+               if (line_result & ROFF_USERRET) {
+                       assert(line_result == (ROFF_IGN | ROFF_USERRET));
+                       if (start == 0) {
+                               /* Return from the current macro. */
+                               result = ROFF_USERRET;
+                               goto out;
+                       }
+               }
+
+               switch (line_result & ROFF_LOOPMASK) {
+               case ROFF_IGN:
+                       break;
+               case ROFF_WHILE:
+                       if (curp->loop != NULL) {
+                               if (loop == curp->loop)
+                                       break;
+                               mandoc_msg(MANDOCERR_WHILE_NEST,
+                                   curp, curp->line, pos, NULL);
+                       }
+                       curp->loop = thisln;
+                       loop = NULL;
+                       inloop = 1;
+                       break;
+               case ROFF_LOOPCONT:
+               case ROFF_LOOPEXIT:
+                       if (curp->loop == NULL) {
+                               mandoc_msg(MANDOCERR_WHILE_FAIL,
+                                   curp, curp->line, pos, NULL);
+                               break;
+                       }
+                       if (inloop == 0) {
+                               mandoc_msg(MANDOCERR_WHILE_INTO,
+                                   curp, curp->line, pos, NULL);
+                               curp->loop = loop = NULL;
+                               break;
+                       }
+                       if (line_result & ROFF_LOOPCONT)
+                               loop = curp->loop;
+                       else {
+                               curp->loop = loop = NULL;
+                               inloop = 0;
+                       }
+                       break;
+               default:
+                       abort();
+               }
+
+               /* Process the main instruction from the roff parser. */
+
+               switch (line_result & ROFF_MASK) {
+               case ROFF_IGN:
+                       break;
+               case ROFF_CONT:
+                       if (curp->man->macroset == MACROSET_NONE)
+                               choose_parser(curp);
+                       if ((curp->man->macroset == MACROSET_MDOC ?
+                            mdoc_parseln(curp->man, curp->line, ln.buf, of) :
+                            man_parseln(curp->man, curp->line, ln.buf, of)
+                           ) == 2)
+                               goto out;
+                       break;
+               case ROFF_RERUN:
+                       goto rerun;
                case ROFF_REPARSE:
-               case ROFF_USERCALL:
                        if (++curp->reparse_count > REPARSE_LIMIT) {
+                               /* Abort and return to the top level. */
                                result = ROFF_IGN;
                                mandoc_msg(MANDOCERR_ROFFLOOP, curp,
                                    curp->line, pos, NULL);
-                       } else {
-                               result = mparse_buf_r(curp, ln, of, 0);
-                               if (line_result == ROFF_USERCALL) {
-                                       if (result == ROFF_USERRET)
-                                               result = ROFF_CONT;
-                                       roff_userret(curp->roff);
-                               }
-                               if (start || result == ROFF_CONT) {
-                                       pos = 0;
-                                       continue;
-                               }
+                               goto out;
                        }
-                       goto out;
-               case ROFF_USERRET:
-                       if (start) {
-                               pos = 0;
-                               continue;
+                       result = mparse_buf_r(curp, ln, of, 0);
+                       if (line_result & ROFF_USERCALL) {
+                               roff_userret(curp->roff);
+                               /* Continue normally. */
+                               if (result & ROFF_USERRET)
+                                       result = ROFF_CONT;
                        }
-                       result = ROFF_USERRET;
-                       goto out;
-               case ROFF_APPEND:
-                       pos = strlen(ln.buf);
-                       continue;
-               case ROFF_RERUN:
-                       goto rerun;
-               case ROFF_IGN:
-                       pos = 0;
-                       continue;
+                       if (start == 0 && result != ROFF_CONT)
+                               goto out;
+                       break;
                case ROFF_SO:
                        if ( ! (curp->options & MPARSE_SO) &&
                            (i >= blk.sz || blk.buf[i] == '\0')) {
@@ -547,30 +605,36 @@ rerun:
                                of = 0;
                                mparse_buf_r(curp, ln, of, 0);
                        }
-                       pos = 0;
-                       continue;
-               default:
                        break;
+               default:
+                       abort();
                }
 
-               if (curp->man->macroset == MACROSET_NONE)
-                       choose_parser(curp);
-
-               if ((curp->man->macroset == MACROSET_MDOC ?
-                   mdoc_parseln(curp->man, curp->line, ln.buf, of) :
-                   man_parseln(curp->man, curp->line, ln.buf, of)) == 2)
-                               break;
-
-               /* Temporary buffers typically are not full. */
-
-               if (0 == start && '\0' == blk.buf[i])
-                       break;
-
                /* Start the next input line. */
 
-               pos = 0;
+               if (loop != NULL &&
+                   (line_result & ROFF_LOOPMASK) == ROFF_IGN)
+                       loop = loop->next;
+
+               if (loop != NULL) {
+                       if ((line_result & ROFF_APPEND) == 0)
+                               *ln.buf = '\0';
+                       if (ln.sz < loop->sz)
+                               resize_buf(&ln, loop->sz);
+                       (void)strlcat(ln.buf, loop->buf, ln.sz);
+                       of = 0;
+                       goto rerun;
+               }
+
+               pos = (line_result & ROFF_APPEND) ? strlen(ln.buf) : 0;
        }
 out:
+       if (inloop) {
+               if (result != ROFF_USERRET)
+                       mandoc_msg(MANDOCERR_WHILE_OUTOF, curp,
+                           curp->line, pos, NULL);
+               curp->loop = NULL;
+       }
        free(ln.buf);
        if (firstln != curp->secondary)
                free_buf_list(firstln);
index 442196e..57ff4a1 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: roff.c,v 1.211 2018/08/23 14:16:12 schwarze Exp $ */
+/*     $OpenBSD: roff.c,v 1.212 2018/08/24 22:56:37 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010-2015, 2017, 2018 Ingo Schwarze <schwarze@openbsd.org>
@@ -141,7 +141,7 @@ struct      roffnode {
                         int pos, /* current pos in buffer */ \
                         int *offs /* reset offset of buffer data */
 
-typedef        enum rofferr (*roffproc)(ROFF_ARGS);
+typedef        int (*roffproc)(ROFF_ARGS);
 
 struct roffmac {
        roffproc         proc; /* process new macro */
@@ -161,26 +161,26 @@ struct    predef {
 
 /* --- function prototypes ------------------------------------------------ */
 
-static void             roffnode_cleanscope(struct roff *);
-static void             roffnode_pop(struct roff *);
+static int              roffnode_cleanscope(struct roff *);
+static int              roffnode_pop(struct roff *);
 static void             roffnode_push(struct roff *, enum roff_tok,
                                const char *, int, int);
 static void             roff_addtbl(struct roff_man *, struct tbl_node *);
-static enum rofferr     roff_als(ROFF_ARGS);
-static enum rofferr     roff_block(ROFF_ARGS);
-static enum rofferr     roff_block_text(ROFF_ARGS);
-static enum rofferr     roff_block_sub(ROFF_ARGS);
-static enum rofferr     roff_br(ROFF_ARGS);
-static enum rofferr     roff_cblock(ROFF_ARGS);
-static enum rofferr     roff_cc(ROFF_ARGS);
-static void             roff_ccond(struct roff *, int, int);
-static enum rofferr     roff_cond(ROFF_ARGS);
-static enum rofferr     roff_cond_text(ROFF_ARGS);
-static enum rofferr     roff_cond_sub(ROFF_ARGS);
-static enum rofferr     roff_ds(ROFF_ARGS);
-static enum rofferr     roff_ec(ROFF_ARGS);
-static enum rofferr     roff_eo(ROFF_ARGS);
-static enum rofferr     roff_eqndelim(struct roff *, struct buf *, int);
+static int              roff_als(ROFF_ARGS);
+static int              roff_block(ROFF_ARGS);
+static int              roff_block_text(ROFF_ARGS);
+static int              roff_block_sub(ROFF_ARGS);
+static int              roff_br(ROFF_ARGS);
+static int              roff_cblock(ROFF_ARGS);
+static int              roff_cc(ROFF_ARGS);
+static int              roff_ccond(struct roff *, int, int);
+static int              roff_cond(ROFF_ARGS);
+static int              roff_cond_text(ROFF_ARGS);
+static int              roff_cond_sub(ROFF_ARGS);
+static int              roff_ds(ROFF_ARGS);
+static int              roff_ec(ROFF_ARGS);
+static int              roff_eo(ROFF_ARGS);
+static int              roff_eqndelim(struct roff *, struct buf *, int);
 static int              roff_evalcond(struct roff *r, int, char *, int *);
 static int              roff_evalnum(struct roff *, int,
                                const char *, int *, int *, int);
@@ -201,42 +201,42 @@ static    const char      *roff_getstrn(struct roff *,
                                const char *, size_t, int *);
 static int              roff_hasregn(const struct roff *,
                                const char *, size_t);
-static enum rofferr     roff_insec(ROFF_ARGS);
-static enum rofferr     roff_it(ROFF_ARGS);
-static enum rofferr     roff_line_ignore(ROFF_ARGS);
+static int              roff_insec(ROFF_ARGS);
+static int              roff_it(ROFF_ARGS);
+static int              roff_line_ignore(ROFF_ARGS);
 static void             roff_man_alloc1(struct roff_man *);
 static void             roff_man_free1(struct roff_man *);
-static enum rofferr     roff_manyarg(ROFF_ARGS);
-static enum rofferr     roff_nop(ROFF_ARGS);
-static enum rofferr     roff_nr(ROFF_ARGS);
-static enum rofferr     roff_onearg(ROFF_ARGS);
+static int              roff_manyarg(ROFF_ARGS);
+static int              roff_nop(ROFF_ARGS);
+static int              roff_nr(ROFF_ARGS);
+static int              roff_onearg(ROFF_ARGS);
 static enum roff_tok    roff_parse(struct roff *, char *, int *,
                                int, int);
-static enum rofferr     roff_parsetext(struct roff *, struct buf *,
+static int              roff_parsetext(struct roff *, struct buf *,
                                int, int *);
-static enum rofferr     roff_renamed(ROFF_ARGS);
-static enum rofferr     roff_res(struct roff *, struct buf *, int, int);
-static enum rofferr     roff_return(ROFF_ARGS);
-static enum rofferr     roff_rm(ROFF_ARGS);
-static enum rofferr     roff_rn(ROFF_ARGS);
-static enum rofferr     roff_rr(ROFF_ARGS);
+static int              roff_renamed(ROFF_ARGS);
+static int              roff_res(struct roff *, struct buf *, int, int);
+static int              roff_return(ROFF_ARGS);
+static int              roff_rm(ROFF_ARGS);
+static int              roff_rn(ROFF_ARGS);
+static int              roff_rr(ROFF_ARGS);
 static void             roff_setregn(struct roff *, const char *,
                                size_t, int, char, int);
 static void             roff_setstr(struct roff *,
                                const char *, const char *, int);
 static void             roff_setstrn(struct roffkv **, const char *,
                                size_t, const char *, size_t, int);
-static enum rofferr     roff_shift(ROFF_ARGS);
-static enum rofferr     roff_so(ROFF_ARGS);
-static enum rofferr     roff_tr(ROFF_ARGS);
-static enum rofferr     roff_Dd(ROFF_ARGS);
-static enum rofferr     roff_TE(ROFF_ARGS);
-static enum rofferr     roff_TS(ROFF_ARGS);
-static enum rofferr     roff_EQ(ROFF_ARGS);
-static enum rofferr     roff_EN(ROFF_ARGS);
-static enum rofferr     roff_T_(ROFF_ARGS);
-static enum rofferr     roff_unsupp(ROFF_ARGS);
-static enum rofferr     roff_userdef(ROFF_ARGS);
+static int              roff_shift(ROFF_ARGS);
+static int              roff_so(ROFF_ARGS);
+static int              roff_tr(ROFF_ARGS);
+static int              roff_Dd(ROFF_ARGS);
+static int              roff_TE(ROFF_ARGS);
+static int              roff_TS(ROFF_ARGS);
+static int              roff_EQ(ROFF_ARGS);
+static int              roff_EN(ROFF_ARGS);
+static int              roff_T_(ROFF_ARGS);
+static int              roff_unsupp(ROFF_ARGS);
+static int              roff_userdef(ROFF_ARGS);
 
 /* --- constant data ------------------------------------------------------ */
 
@@ -588,7 +588,7 @@ static      struct roffmac   roffs[TOKEN_NONE] = {
        { roff_line_ignore, NULL, NULL, 0 },  /* watchlength */
        { roff_line_ignore, NULL, NULL, 0 },  /* watchn */
        { roff_unsupp, NULL, NULL, 0 },  /* wh */
-       { roff_unsupp, NULL, NULL, 0 },  /* while */
+       { roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT }, /*while*/
        { roff_insec, NULL, NULL, 0 },  /* write */
        { roff_insec, NULL, NULL, 0 },  /* writec */
        { roff_insec, NULL, NULL, 0 },  /* writem */
@@ -672,18 +672,19 @@ roffhash_find(struct ohash *htab, const char *name, size_t sz)
  * Pop the current node off of the stack of roff instructions currently
  * pending.
  */
-static void
+static int
 roffnode_pop(struct roff *r)
 {
        struct roffnode *p;
+       int              inloop;
 
-       assert(r->last);
        p = r->last;
-
-       r->last = r->last->parent;
+       inloop = p->tok == ROFF_while;
+       r->last = p->parent;
        free(p->name);
        free(p->end);
        free(p);
+       return inloop;
 }
 
 /*
@@ -1142,7 +1143,7 @@ deroff(char **dest, const struct roff_node *n)
  * used in numerical expressions and conditional requests.
  * Also check the syntax of the remaining escape sequences.
  */
-static enum rofferr
+static int
 roff_res(struct roff *r, struct buf *buf, int ln, int pos)
 {
        struct mctx     *ctx;   /* current macro call context */
@@ -1234,7 +1235,7 @@ roff_res(struct roff *r, struct buf *buf, int ln, int pos)
 
                if (stesc[1] == '#') {
                        *stesc = '\0';
-                       return ROFF_APPEND;
+                       return ROFF_IGN | ROFF_APPEND;
                }
 
                /* Discard normal comments. */
@@ -1292,7 +1293,7 @@ roff_res(struct roff *r, struct buf *buf, int ln, int pos)
                        if (done)
                                continue;
                        else
-                               return ROFF_APPEND;
+                               return ROFF_IGN | ROFF_APPEND;
                }
 
                /* Decide whether to expand or to check only. */
@@ -1527,7 +1528,7 @@ roff_res(struct roff *r, struct buf *buf, int ln, int pos)
 /*
  * Process text streams.
  */
-static enum rofferr
+static int
 roff_parsetext(struct roff *r, struct buf *buf, int pos, int *offs)
 {
        size_t           sz;
@@ -1593,11 +1594,11 @@ roff_parsetext(struct roff *r, struct buf *buf, int pos, int *offs)
        return ROFF_CONT;
 }
 
-enum rofferr
+int
 roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs)
 {
        enum roff_tok    t;
-       enum rofferr     e;
+       int              e;
        int              pos;   /* parse point */
        int              spos;  /* saved parse point for messages */
        int              ppos;  /* original offset in buf->buf */
@@ -1619,7 +1620,7 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs)
        /* Expand some escape sequences. */
 
        e = roff_res(r, buf, ln, pos);
-       if (e == ROFF_IGN || e == ROFF_APPEND)
+       if ((e & ROFF_MASK) == ROFF_IGN)
                return e;
        assert(e == ROFF_CONT);
 
@@ -1636,21 +1637,22 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs)
        if (r->last != NULL && ! ctl) {
                t = r->last->tok;
                e = (*roffs[t].text)(r, t, buf, ln, pos, pos, offs);
-               if (e == ROFF_IGN)
+               if ((e & ROFF_MASK) == ROFF_IGN)
                        return e;
-               assert(e == ROFF_CONT);
-       }
+               e &= ~ROFF_MASK;
+       } else
+               e = ROFF_IGN;
        if (r->eqn != NULL && strncmp(buf->buf + ppos, ".EN", 3)) {
                eqn_read(r->eqn, buf->buf + ppos);
-               return ROFF_IGN;
+               return e;
        }
        if (r->tbl != NULL && (ctl == 0 || buf->buf[pos] == '\0')) {
                tbl_read(r->tbl, ln, buf->buf, ppos);
                roff_addtbl(r->man, r->tbl);
-               return ROFF_IGN;
+               return e;
        }
        if ( ! ctl)
-               return roff_parsetext(r, buf, pos, offs);
+               return roff_parsetext(r, buf, pos, offs) | e;
 
        /* Skip empty request lines. */
 
@@ -1806,7 +1808,7 @@ roff_parse(struct roff *r, char *buf, int *pos, int ln, int ppos)
 
 /* --- handling of request blocks ----------------------------------------- */
 
-static enum rofferr
+static int
 roff_cblock(ROFF_ARGS)
 {
 
@@ -1846,50 +1848,51 @@ roff_cblock(ROFF_ARGS)
 
 }
 
-static void
+static int
 roffnode_cleanscope(struct roff *r)
 {
+       int inloop;
 
-       while (r->last) {
+       inloop = 0;
+       while (r->last != NULL) {
                if (--r->last->endspan != 0)
                        break;
-               roffnode_pop(r);
+               inloop += roffnode_pop(r);
        }
+       return inloop;
 }
 
-static void
+static int
 roff_ccond(struct roff *r, int ln, int ppos)
 {
-
        if (NULL == r->last) {
                mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
                    ln, ppos, "\\}");
-               return;
+               return 0;
        }
 
        switch (r->last->tok) {
        case ROFF_el:
        case ROFF_ie:
        case ROFF_if:
+       case ROFF_while:
                break;
        default:
                mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
                    ln, ppos, "\\}");
-               return;
+               return 0;
        }
 
        if (r->last->endspan > -1) {
                mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
                    ln, ppos, "\\}");
-               return;
+               return 0;
        }
 
-       roffnode_pop(r);
-       roffnode_cleanscope(r);
-       return;
+       return roffnode_pop(r) + roffnode_cleanscope(r);
 }
 
-static enum rofferr
+static int
 roff_block(ROFF_ARGS)
 {
        const char      *name, *value;
@@ -2014,7 +2017,7 @@ roff_block(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_block_sub(ROFF_ARGS)
 {
        enum roff_tok   t;
@@ -2068,7 +2071,7 @@ roff_block_sub(ROFF_ARGS)
        return (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs);
 }
 
-static enum rofferr
+static int
 roff_block_text(ROFF_ARGS)
 {
 
@@ -2078,15 +2081,19 @@ roff_block_text(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_cond_sub(ROFF_ARGS)
 {
-       enum roff_tok    t;
        char            *ep;
-       int              rr;
+       int              endloop, irc, rr;
+       enum roff_tok    t;
 
+       irc = ROFF_IGN;
        rr = r->last->rule;
-       roffnode_cleanscope(r);
+       endloop = tok != ROFF_while ? ROFF_IGN :
+           rr ? ROFF_LOOPCONT : ROFF_LOOPEXIT;
+       if (roffnode_cleanscope(r))
+               irc |= endloop;
 
        /*
         * If `\}' occurs on a macro line without a preceding macro,
@@ -2103,7 +2110,8 @@ roff_cond_sub(ROFF_ARGS)
                switch (ep[1]) {
                case '}':
                        memmove(ep, ep + 2, strlen(ep + 2) + 1);
-                       roff_ccond(r, ln, ep - buf->buf);
+                       if (roff_ccond(r, ln, ep - buf->buf))
+                               irc |= endloop;
                        break;
                case '\0':
                        ++ep;
@@ -2120,30 +2128,38 @@ roff_cond_sub(ROFF_ARGS)
         */
 
        t = roff_parse(r, buf->buf, &pos, ln, ppos);
-       return t != TOKEN_NONE && (rr || roffs[t].flags & ROFFMAC_STRUCT)
-           ? (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs) : rr
-           ? ROFF_CONT : ROFF_IGN;
+       irc |= t != TOKEN_NONE && (rr || roffs[t].flags & ROFFMAC_STRUCT) ?
+           (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs) :
+           rr ? ROFF_CONT : ROFF_IGN;
+       return irc;
 }
 
-static enum rofferr
+static int
 roff_cond_text(ROFF_ARGS)
 {
        char            *ep;
-       int              rr;
+       int              endloop, irc, rr;
 
+       irc = ROFF_IGN;
        rr = r->last->rule;
-       roffnode_cleanscope(r);
+       endloop = tok != ROFF_while ? ROFF_IGN :
+           rr ? ROFF_LOOPCONT : ROFF_LOOPEXIT;
+       if (roffnode_cleanscope(r))
+               irc |= endloop;
 
        ep = buf->buf + pos;
        while ((ep = strchr(ep, '\\')) != NULL) {
                if (*(++ep) == '}') {
                        *ep = '&';
-                       roff_ccond(r, ln, ep - buf->buf - 1);
+                       if (roff_ccond(r, ln, ep - buf->buf - 1))
+                               irc |= endloop;
                }
                if (*ep != '\0')
                        ++ep;
        }
-       return rr ? ROFF_CONT : ROFF_IGN;
+       if (rr)
+               irc |= ROFF_CONT;
+       return irc;
 }
 
 /* --- handling of numeric and conditional expressions -------------------- */
@@ -2363,14 +2379,14 @@ roff_evalcond(struct roff *r, int ln, char *v, int *pos)
                return 0;
 }
 
-static enum rofferr
+static int
 roff_line_ignore(ROFF_ARGS)
 {
 
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_insec(ROFF_ARGS)
 {
 
@@ -2379,7 +2395,7 @@ roff_insec(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_unsupp(ROFF_ARGS)
 {
 
@@ -2388,9 +2404,10 @@ roff_unsupp(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_cond(ROFF_ARGS)
 {
+       int      irc;
 
        roffnode_push(r, tok, NULL, ln, ppos);
 
@@ -2429,9 +2446,10 @@ roff_cond(ROFF_ARGS)
         * Determine scope.
         * If there is nothing on the line after the conditional,
         * not even whitespace, use next-line scope.
+        * Except that .while does not support next-line scope.
         */
 
-       if (buf->buf[pos] == '\0') {
+       if (buf->buf[pos] == '\0' && tok != ROFF_while) {
                r->last->endspan = 2;
                goto out;
        }
@@ -2463,10 +2481,13 @@ roff_cond(ROFF_ARGS)
 
 out:
        *offs = pos;
-       return ROFF_RERUN;
+       irc = ROFF_RERUN;
+       if (tok == ROFF_while)
+               irc |= ROFF_WHILE;
+       return irc;
 }
 
-static enum rofferr
+static int
 roff_ds(ROFF_ARGS)
 {
        char            *string;
@@ -2855,7 +2876,7 @@ roff_freereg(struct roffreg *reg)
        }
 }
 
-static enum rofferr
+static int
 roff_nr(ROFF_ARGS)
 {
        char            *key, *val, *step;
@@ -2889,7 +2910,7 @@ roff_nr(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_rr(ROFF_ARGS)
 {
        struct roffreg  *reg, **prev;
@@ -2919,7 +2940,7 @@ roff_rr(ROFF_ARGS)
 
 /* --- handler functions for roff requests -------------------------------- */
 
-static enum rofferr
+static int
 roff_rm(ROFF_ARGS)
 {
        const char       *name;
@@ -2938,7 +2959,7 @@ roff_rm(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_it(ROFF_ARGS)
 {
        int              iv;
@@ -2967,7 +2988,7 @@ roff_it(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_Dd(ROFF_ARGS)
 {
        int              mask;
@@ -2997,7 +3018,7 @@ roff_Dd(ROFF_ARGS)
        return ROFF_CONT;
 }
 
-static enum rofferr
+static int
 roff_TE(ROFF_ARGS)
 {
        if (r->tbl == NULL) {
@@ -3017,7 +3038,7 @@ roff_TE(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_T_(ROFF_ARGS)
 {
 
@@ -3033,7 +3054,7 @@ roff_T_(ROFF_ARGS)
 /*
  * Handle in-line equation delimiters.
  */
-static enum rofferr
+static int
 roff_eqndelim(struct roff *r, struct buf *buf, int pos)
 {
        char            *cp1, *cp2;
@@ -3096,7 +3117,7 @@ roff_eqndelim(struct roff *r, struct buf *buf, int pos)
        return ROFF_REPARSE;
 }
 
-static enum rofferr
+static int
 roff_EQ(ROFF_ARGS)
 {
        struct roff_node        *n;
@@ -3126,7 +3147,7 @@ roff_EQ(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_EN(ROFF_ARGS)
 {
        if (r->eqn != NULL) {
@@ -3140,7 +3161,7 @@ roff_EN(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_TS(ROFF_ARGS)
 {
        if (r->tbl != NULL) {
@@ -3157,7 +3178,7 @@ roff_TS(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_onearg(ROFF_ARGS)
 {
        struct roff_node        *n;
@@ -3217,7 +3238,7 @@ roff_onearg(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_manyarg(ROFF_ARGS)
 {
        struct roff_node        *n;
@@ -3240,7 +3261,7 @@ roff_manyarg(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_als(ROFF_ARGS)
 {
        char            *oldn, *newn, *end, *value;
@@ -3267,7 +3288,7 @@ roff_als(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_br(ROFF_ARGS)
 {
        if (r->man->flags & (MAN_BLINE | MAN_ELINE))
@@ -3281,7 +3302,7 @@ roff_br(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_cc(ROFF_ARGS)
 {
        const char      *p;
@@ -3298,7 +3319,7 @@ roff_cc(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_ec(ROFF_ARGS)
 {
        const char      *p;
@@ -3315,7 +3336,7 @@ roff_ec(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_eo(ROFF_ARGS)
 {
        r->escape = '\0';
@@ -3325,7 +3346,7 @@ roff_eo(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_nop(ROFF_ARGS)
 {
        while (buf->buf[pos] == ' ')
@@ -3334,7 +3355,7 @@ roff_nop(ROFF_ARGS)
        return ROFF_RERUN;
 }
 
-static enum rofferr
+static int
 roff_tr(ROFF_ARGS)
 {
        const char      *p, *first, *second;
@@ -3402,17 +3423,17 @@ roff_tr(ROFF_ARGS)
  * The read module will call that after rewinding the reader stack
  * to the place from where the current macro was called.
  */
-static enum rofferr
+static int
 roff_return(ROFF_ARGS)
 {
        if (r->mstackpos >= 0)
-               return ROFF_USERRET;
+               return ROFF_IGN | ROFF_USERRET;
 
        mandoc_msg(MANDOCERR_REQ_NOMAC, r->parse, ln, ppos, "return");
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_rn(ROFF_ARGS)
 {
        const char      *value;
@@ -3462,7 +3483,7 @@ roff_rn(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_shift(ROFF_ARGS)
 {
        struct mctx     *ctx;
@@ -3495,7 +3516,7 @@ roff_shift(ROFF_ARGS)
        return ROFF_IGN;
 }
 
-static enum rofferr
+static int
 roff_so(ROFF_ARGS)
 {
        char *name, *cp;
@@ -3527,7 +3548,7 @@ roff_so(ROFF_ARGS)
 
 /* --- user defined strings and macros ------------------------------------ */
 
-static enum rofferr
+static int
 roff_userdef(ROFF_ARGS)
 {
        struct mctx      *ctx;
@@ -3581,14 +3602,14 @@ roff_userdef(ROFF_ARGS)
        *offs = 0;
 
        return buf->sz > 1 && buf->buf[buf->sz - 2] == '\n' ?
-          ROFF_USERCALL : ROFF_APPEND;
+           ROFF_REPARSE | ROFF_USERCALL : ROFF_IGN | ROFF_APPEND;
 }
 
 /*
  * Calling a high-level macro that was renamed with .rn.
  * r->current_string has already been set up by roff_parse().
  */
-static enum rofferr
+static int
 roff_renamed(ROFF_ARGS)
 {
        char    *nbuf;