summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIngo Schwarze <schwarze@openbsd.org>2019-04-21 22:48:58 +0000
committerIngo Schwarze <schwarze@openbsd.org>2019-04-21 22:48:58 +0000
commitee05fc6b9d7afc49d5425027d5c9fd1a99e75ca8 (patch)
tree1a0ec30ac83c53723a156c4e3f5c28b0c525bfb0
parent06197a5b46b695be6f9f5bf8f2cd0ef405e227b8 (diff)
downloadmandoc-ee05fc6b9d7afc49d5425027d5c9fd1a99e75ca8.tar.gz
Implement the roff .break request (break out of a .while loop).
Jan Stary <hans at stare dot cz> found it in an ancient groffer(1) manual page (version 1.19) on MacOS X Mojave. Having .break not implemented wasn't a particularly bright idea because obviously, it tended to cause infinite loops.
-rw-r--r--regress/roff/while/Makefile4
-rw-r--r--regress/roff/while/break.in16
-rw-r--r--regress/roff/while/break.out_ascii9
-rw-r--r--roff.73
-rw-r--r--roff.c53
5 files changed, 73 insertions, 12 deletions
diff --git a/regress/roff/while/Makefile b/regress/roff/while/Makefile
index ed970474..42ce590a 100644
--- a/regress/roff/while/Makefile
+++ b/regress/roff/while/Makefile
@@ -1,6 +1,6 @@
-# $OpenBSD: Makefile,v 1.1 2018/08/24 22:56:37 schwarze Exp $
+# $OpenBSD: Makefile,v 1.2 2019/04/21 22:43:00 schwarze Exp $
-REGRESS_TARGETS = basic badargs into nesting outof
+REGRESS_TARGETS = basic badargs break into nesting outof
LINT_TARGETS = badargs into nesting outof
# mandoc defects:
diff --git a/regress/roff/while/break.in b/regress/roff/while/break.in
new file mode 100644
index 00000000..bfc8bc3f
--- /dev/null
+++ b/regress/roff/while/break.in
@@ -0,0 +1,16 @@
+.\" $OpenBSD: break.in,v 1.1 2019/04/21 22:43:00 schwarze Exp $
+.Dd $Mdocdate$
+.Dt WHILE-BREAK 1
+.Os
+.Sh NAME
+.Nm while-break
+.Nd break request inside a while loop
+.Sh DESCRIPTION
+initial text
+.nr cnt 11 1
+.while n \{\
+\n-[cnt]
+.if !\n[cnt] .break
+\(en
+.\}
+final text
diff --git a/regress/roff/while/break.out_ascii b/regress/roff/while/break.out_ascii
new file mode 100644
index 00000000..8e3f39b9
--- /dev/null
+++ b/regress/roff/while/break.out_ascii
@@ -0,0 +1,9 @@
+WHILE-BREAK(1) General Commands Manual WHILE-BREAK(1)
+
+NNAAMMEE
+ wwhhiillee--bbrreeaakk - break request inside a while loop
+
+DDEESSCCRRIIPPTTIIOONN
+ initial text 10 - 9 - 8 - 7 - 6 - 5 - 4 - 3 - 2 - 1 - 0 final text
+
+OpenBSD April 21, 2019 OpenBSD
diff --git a/roff.7 b/roff.7
index ee1981a8..a6e1c8d4 100644
--- a/roff.7
+++ b/roff.7
@@ -503,10 +503,9 @@ This is a Heirloom extension and currently unsupported.
.It Ic \&br
Break the output line.
.It Ic \&break
-Break out of a
+Break out of the innermost
.Ic \&while
loop.
-Currently unsupported.
.It Ic \&breakchar Ar char ...
Optional line break characters.
This is a Heirloom extension and currently ignored.
diff --git a/roff.c b/roff.c
index 0766abad..9744a01f 100644
--- a/roff.c
+++ b/roff.c
@@ -133,15 +133,18 @@ struct roff {
char escape; /* escape character */
};
+/*
+ * A macro definition, condition, or ignored block.
+ */
struct roffnode {
enum roff_tok tok; /* type of node */
struct roffnode *parent; /* up one in stack */
int line; /* parse line */
int col; /* parse col */
char *name; /* node name, e.g. macro name */
- char *end; /* end-rules: custom token */
- int endspan; /* end-rules: next-line or infty */
- int rule; /* current evaluation rule */
+ char *end; /* custom end macro of the block */
+ int endspan; /* scope to: 1=eol 2=next line -1=\} */
+ int rule; /* content is: 1=evaluated 0=skipped */
};
#define ROFF_ARGS struct roff *r, /* parse ctx */ \
@@ -181,6 +184,7 @@ 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_break(ROFF_ARGS);
static int roff_cblock(ROFF_ARGS);
static int roff_cc(ROFF_ARGS);
static int roff_ccond(struct roff *, int, int);
@@ -400,7 +404,7 @@ static struct roffmac roffs[TOKEN_NONE] = {
{ roff_unsupp, NULL, NULL, 0 }, /* boxa */
{ roff_line_ignore, NULL, NULL, 0 }, /* bp */
{ roff_unsupp, NULL, NULL, 0 }, /* BP */
- { roff_unsupp, NULL, NULL, 0 }, /* break */
+ { roff_break, NULL, NULL, 0 }, /* break */
{ roff_line_ignore, NULL, NULL, 0 }, /* breakchar */
{ roff_line_ignore, NULL, NULL, 0 }, /* brnl */
{ roff_noarg, NULL, NULL, 0 }, /* brp */
@@ -685,7 +689,7 @@ roffhash_find(struct ohash *htab, const char *name, size_t sz)
/*
* Pop the current node off of the stack of roff instructions currently
- * pending.
+ * pending. Return 1 if it is a loop or 0 otherwise.
*/
static int
roffnode_pop(struct roff *r)
@@ -2002,6 +2006,10 @@ roff_cblock(ROFF_ARGS)
}
+/*
+ * Pop all nodes ending at the end of the current input line.
+ * Return the number of loops ended.
+ */
static int
roffnode_cleanscope(struct roff *r)
{
@@ -2016,6 +2024,11 @@ roffnode_cleanscope(struct roff *r)
return inloop;
}
+/*
+ * Handle the closing \} of a conditional block.
+ * Apart from generating warnings, this only pops nodes.
+ * Return the number of loops ended.
+ */
static int
roff_ccond(struct roff *r, int ln, int ppos)
{
@@ -2235,6 +2248,7 @@ roff_block_text(ROFF_ARGS)
static int
roff_cond_sub(ROFF_ARGS)
{
+ struct roffnode *bl;
char *ep;
int endloop, irc, rr;
enum roff_tok t;
@@ -2282,9 +2296,21 @@ roff_cond_sub(ROFF_ARGS)
*/
t = roff_parse(r, buf->buf, &pos, ln, ppos);
- 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;
+ if (t == ROFF_break) {
+ if (irc & ROFF_LOOPMASK)
+ irc = ROFF_IGN | ROFF_LOOPEXIT;
+ else if (rr) {
+ for (bl = r->last; bl != NULL; bl = bl->parent) {
+ bl->rule = 0;
+ if (bl->tok == ROFF_while)
+ break;
+ }
+ }
+ } else if (t != TOKEN_NONE &&
+ (rr || roffs[t].flags & ROFFMAC_STRUCT))
+ irc |= (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs);
+ else
+ irc |= rr ? ROFF_CONT : ROFF_IGN;
return irc;
}
@@ -3482,6 +3508,17 @@ roff_als(ROFF_ARGS)
return ROFF_IGN;
}
+/*
+ * The .break request only makes sense inside conditionals,
+ * and that case is already handled in roff_cond_sub().
+ */
+static int
+roff_break(ROFF_ARGS)
+{
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, pos, "break");
+ return ROFF_IGN;
+}
+
static int
roff_cc(ROFF_ARGS)
{