source@mandoc.bsd.lv
 help / color / mirror / Atom feed
* mandoc: Implement the roff(7) .shift and .return requests, for example
@ 2018-08-23 14:30 schwarze
  0 siblings, 0 replies; only message in thread
From: schwarze @ 2018-08-23 14:30 UTC (permalink / raw)
  To: source

Log Message:
-----------
Implement the roff(7) .shift and .return requests,
for example used by groff_hdtbl(7) and groff_mom(7).

Also correctly interpolate arguments during nested macro execution
even after .shift and .return, implemented using a stack of argument
arrays.

Note that only read.c, but not roff.c can detect the end of a macro
execution, and the existence of .shift implies that arguments cannot
be interpolated up front, so unfortunately, this includes a partial
revert of roff.c rev. 1.337, moving argument interpolation back into
the function roff_res().

Modified Files:
--------------
    mandoc:
        TODO
        libmandoc.h
        mandoc.1
        mandoc.h
        read.c
        roff.7
        roff.c
    mandoc/regress/roff:
        Makefile
    mandoc/regress/roff/de:
        infinite.in
        infinite.out_ascii
        infinite.out_lint

Added Files:
-----------
    mandoc/regress/roff/return:
        Makefile
        basic.in
        basic.out_ascii
        basic.out_lint
    mandoc/regress/roff/shift:
        Makefile
        bad.in
        bad.out_ascii
        bad.out_lint
        basic.in
        basic.out_ascii

Revision Data
-------------
Index: infinite.out_ascii
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/roff/de/infinite.out_ascii,v
retrieving revision 1.3
retrieving revision 1.4
diff -Lregress/roff/de/infinite.out_ascii -Lregress/roff/de/infinite.out_ascii -u -p -r1.3 -r1.4
--- regress/roff/de/infinite.out_ascii
+++ regress/roff/de/infinite.out_ascii
@@ -4,6 +4,6 @@ N\bNA\bAM\bME\bE
      d\bde\be-\b-i\bin\bnf\bfi\bin\bni\bit\bte\be - inifinte recursion in a user-defined macro
 
 D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
-     initial text [$1 $1] middle text final text
+     initial text [$1 end] [middle end] middle text final text
 
-OpenBSD                          July 4, 2017                          OpenBSD
+OpenBSD                         August 23, 2018                        OpenBSD
Index: infinite.in
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/roff/de/infinite.in,v
retrieving revision 1.3
retrieving revision 1.4
diff -Lregress/roff/de/infinite.in -Lregress/roff/de/infinite.in -u -p -r1.3 -r1.4
--- regress/roff/de/infinite.in
+++ regress/roff/de/infinite.in
@@ -1,4 +1,4 @@
-.\" $OpenBSD: infinite.in,v 1.3 2017/07/04 14:53:27 schwarze Exp $
+.\" $OpenBSD: infinite.in,v 1.4 2018/08/23 14:16:12 schwarze Exp $
 .Dd $Mdocdate$
 .Dt DE-INFINITE 1
 .Os
@@ -10,8 +10,8 @@ initial text
 .de mym
 .Op \\$1 \\$2
 ..
-.mym $1 \$1
-.mym \$1 nothing
+.mym $1 \$1 end
+.mym \$1 middle end
 middle text
 .de mym
 .mym
Index: infinite.out_lint
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/roff/de/infinite.out_lint,v
retrieving revision 1.6
retrieving revision 1.7
diff -Lregress/roff/de/infinite.out_lint -Lregress/roff/de/infinite.out_lint -u -p -r1.6 -r1.7
--- regress/roff/de/infinite.out_lint
+++ regress/roff/de/infinite.out_lint
@@ -1,2 +1,3 @@
-mandoc: infinite.in:14:5: ERROR: input stack limit exceeded, infinite loop?
+mandoc: infinite.in:13:9: ERROR: using macro argument outside macro: \$1
+mandoc: infinite.in:14:6: ERROR: using macro argument outside macro: \$1
 mandoc: infinite.in:20:5: ERROR: input stack limit exceeded, infinite loop?
Index: read.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/read.c,v
retrieving revision 1.196
retrieving revision 1.197
diff -Lread.c -Lread.c -u -p -r1.196 -r1.197
--- read.c
+++ read.c
@@ -62,7 +62,7 @@ struct	mparse {
 
 static	void	  choose_parser(struct mparse *);
 static	void	  resize_buf(struct buf *, size_t);
-static	int	  mparse_buf_r(struct mparse *, struct buf, size_t, int);
+static	enum rofferr 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 *);
@@ -233,6 +233,7 @@ static	const char * const	mandocerrs[MAN
 	"input stack limit exceeded, infinite loop?",
 	"skipping bad character",
 	"skipping unknown macro",
+	"ignoring request outside macro",
 	"skipping insecure request",
 	"skipping item outside list",
 	"skipping column outside column list",
@@ -243,6 +244,8 @@ static	const char * const	mandocerrs[MAN
 
 	/* related to request and macro arguments */
 	"escaped character not allowed in a name",
+	"using macro argument outside macro",
+	"argument number is not numeric",
 	"NOT IMPLEMENTED: Bd -file",
 	"skipping display without arguments",
 	"missing list type, using -item",
@@ -251,6 +254,7 @@ static	const char * const	mandocerrs[MAN
 	"uname(3) system call failed, using UNKNOWN",
 	"unknown standard specifier",
 	"skipping request without numeric argument",
+	"excessive shift",
 	"NOT IMPLEMENTED: .so with absolute path or \"..\"",
 	".so request failed",
 	"skipping all arguments",
@@ -338,14 +342,14 @@ choose_parser(struct mparse *curp)
  * macros, inline equations, and input line traps)
  * and indirectly (for .so file inclusion).
  */
-static int
+static enum rofferr
 mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start)
 {
 	struct buf	 ln;
 	const char	*save_file;
 	char		*cp;
 	size_t		 pos; /* byte number in the ln buffer */
-	enum rofferr	 rr;
+	enum rofferr	 line_result, sub_result;
 	int		 of;
 	int		 lnn; /* line number in the real file */
 	int		 fd;
@@ -468,20 +472,36 @@ mparse_buf_r(struct mparse *curp, struct
 				[curp->secondary->sz] = '\0';
 		}
 rerun:
-		rr = roff_parseln(curp->roff, curp->line, &ln, &of);
+		line_result = roff_parseln(curp->roff, curp->line, &ln, &of);
 
-		switch (rr) {
+		switch (line_result) {
 		case ROFF_REPARSE:
-			if (++curp->reparse_count > REPARSE_LIMIT)
+		case ROFF_USERCALL:
+			if (++curp->reparse_count > REPARSE_LIMIT) {
+				sub_result = ROFF_IGN;
 				mandoc_msg(MANDOCERR_ROFFLOOP, curp,
 				    curp->line, pos, NULL);
-			else if (mparse_buf_r(curp, ln, of, 0) == 1 ||
-			    start == 1) {
+			} else {
+				sub_result = mparse_buf_r(curp, ln, of, 0);
+				if (line_result == ROFF_USERCALL) {
+					if (sub_result == ROFF_USERRET)
+						sub_result = ROFF_CONT;
+					roff_userret(curp->roff);
+				}
+				if (start || sub_result == ROFF_CONT) {
+					pos = 0;
+					continue;
+				}
+			}
+			free(ln.buf);
+			return sub_result;
+		case ROFF_USERRET:
+			if (start) {
 				pos = 0;
 				continue;
 			}
 			free(ln.buf);
-			return 0;
+			return ROFF_USERRET;
 		case ROFF_APPEND:
 			pos = strlen(ln.buf);
 			continue;
@@ -495,7 +515,7 @@ rerun:
 			    (i >= blk.sz || blk.buf[i] == '\0')) {
 				curp->sodest = mandoc_strdup(ln.buf + of);
 				free(ln.buf);
-				return 1;
+				return ROFF_CONT;
 			}
 			/*
 			 * We remove `so' clauses from our lookaside
@@ -547,7 +567,7 @@ rerun:
 	}
 
 	free(ln.buf);
-	return 1;
+	return ROFF_CONT;
 }
 
 static int
Index: TODO
===================================================================
RCS file: /home/cvs/mandoc/mandoc/TODO,v
retrieving revision 1.267
retrieving revision 1.268
diff -LTODO -LTODO -u -p -r1.267 -r1.268
--- TODO
+++ TODO
@@ -57,7 +57,7 @@ are mere guesses, and some may be wrong.
   reported by brad@  Sat, 15 Jan 2011 15:45:23 -0500
   loc ***  exist ***  algo ***  size **  imp *
 
-- .while and .shift
+- .while
   found by jca@ in ratpoison(1)  Sun, 30 Jun 2013 12:01:09 +0200
   loc *  exist **  algo **  size **  imp **
 
Index: mandoc.1
===================================================================
RCS file: /home/cvs/mandoc/mandoc/mandoc.1,v
retrieving revision 1.226
retrieving revision 1.227
diff -Lmandoc.1 -Lmandoc.1 -u -p -r1.226 -r1.227
--- mandoc.1
+++ mandoc.1
@@ -1807,6 +1807,13 @@ or
 macro.
 It may be mistyped or unsupported.
 The request or macro is discarded including its arguments.
+.It Sy "skipping request outside macro"
+.Pq roff
+A
+.Ic shift
+or
+.Ic return
+request occurs outside any macro definition and has no effect.
 .It Sy "skipping insecure request"
 .Pq roff
 An input file attempted to run a shell command
@@ -1916,6 +1923,14 @@ When parsing for a request or a user-def
 only the escape sequence is discarded.
 The characters preceding it are used as the request or macro name,
 the characters following it are used as the arguments to the request or macro.
+.It Sy "using macro argument outside macro"
+.Pq roff
+The escape sequence \e$ occurs outside any macro definition
+and expands to the empty string.
+.It Sy "argument number is not numeric"
+.Pq roff
+The argument of the escape sequence \e$ is not a digit;
+the escape sequence expands to the empty string.
 .It Sy "NOT IMPLEMENTED: Bd -file"
 .Pq mdoc
 For security reasons, the
@@ -1978,6 +1993,13 @@ or
 .Ic \&gsize
 statement has a non-numeric or negative argument or no argument at all.
 The invalid request or statement is ignored.
+.It Sy "excessive shift"
+.Pq roff
+The argument of a
+.Ic shift
+request is larger than the number of arguments of the macro that is
+currently being executed.
+All macro arguments are deleted and \en(.$ is set to zero.
 .It Sy "NOT IMPLEMENTED: .so with absolute path or \(dq..\(dq"
 .Pq roff
 For security reasons,
Index: roff.7
===================================================================
RCS file: /home/cvs/mandoc/mandoc/roff.7,v
retrieving revision 1.102
retrieving revision 1.103
diff -Lroff.7 -Lroff.7 -u -p -r1.102 -r1.103
--- roff.7
+++ roff.7
@@ -1472,8 +1472,8 @@ Currently ignored.
 Set the maximum stack depth for recursive macros.
 This is a Heirloom extension and currently ignored.
 .It Ic \&return Op Ar twice
-Exit a macro and return to the caller.
-Currently unsupported.
+Exit the presently executed macro and return to the caller.
+The argument is currently ignored.
 .It Ic \&rfschar Ar font glyph ...
 Remove font-specific fallback glyph definitions.
 Currently unsupported.
@@ -1522,8 +1522,11 @@ This is a Heirloom extension and current
 Change the soft hyphen character.
 Currently ignored.
 .It Ic \&shift Op Ar number
-Shift macro arguments.
-Currently unsupported.
+Shift macro arguments
+.Ar number
+times, by default once: \e\e$i becomes what \e\e$i+number was.
+Also decrement \en(.$ by
+.Ar number .
 .It Ic \&sizes Ar size ...
 Define permissible point sizes.
 This is a groff extension and currently ignored.
Index: libmandoc.h
===================================================================
RCS file: /home/cvs/mandoc/mandoc/libmandoc.h,v
retrieving revision 1.71
retrieving revision 1.72
diff -Llibmandoc.h -Llibmandoc.h -u -p -r1.71 -r1.72
--- libmandoc.h
+++ libmandoc.h
@@ -1,7 +1,7 @@
 /*	$Id$ */
 /*
  * Copyright (c) 2009, 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
- * Copyright (c) 2013, 2014, 2015, 2017 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2013,2014,2015,2017,2018 Ingo Schwarze <schwarze@openbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -21,6 +21,8 @@ enum	rofferr {
 	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 */
 };
@@ -64,6 +66,7 @@ struct roff_man	*roff_man_alloc(struct r
 			const char *, int);
 void		 roff_man_reset(struct roff_man *);
 enum rofferr	 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);
 int		 roff_getreg(struct roff *, const char *);
Index: roff.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/roff.c,v
retrieving revision 1.338
retrieving revision 1.339
diff -Lroff.c -Lroff.c -u -p -r1.338 -r1.339
--- roff.c
+++ roff.c
@@ -85,10 +85,21 @@ struct	roffreq {
 	char		 name[];
 };
 
+/*
+ * A macro processing context.
+ * More than one is needed when macro calls are nested.
+ */
+struct	mctx {
+	char		**argv;
+	int		 argc;
+	int		 argsz;
+};
+
 struct	roff {
 	struct mparse	*parse; /* parse point */
 	struct roff_man	*man; /* mdoc or man parser */
 	struct roffnode	*last; /* leaf of stack */
+	struct mctx	*mstack; /* stack of macro contexts */
 	int		*rstack; /* stack of inverted `ie' values */
 	struct ohash	*reqtab; /* request lookup table */
 	struct roffreg	*regtab; /* number registers */
@@ -104,6 +115,8 @@ struct	roff {
 	struct eqn_node	*eqn; /* active equation parser */
 	int		 eqn_inline; /* current equation is inline */
 	int		 options; /* parse options */
+	int		 mstacksz; /* current size of mstack */
+	int		 mstackpos; /* position in mstack */
 	int		 rstacksz; /* current size limit of rstack */
 	int		 rstackpos; /* position in rstack */
 	int		 format; /* current file in mdoc or man format */
@@ -205,6 +218,7 @@ static	enum rofferr	 roff_parsetext(stru
 				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);
@@ -214,6 +228,7 @@ 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);
@@ -521,7 +536,7 @@ static	struct roffmac	 roffs[TOKEN_NONE]
 	{ roff_unsupp, NULL, NULL, 0 },  /* rchar */
 	{ roff_line_ignore, NULL, NULL, 0 },  /* rd */
 	{ roff_line_ignore, NULL, NULL, 0 },  /* recursionlimit */
-	{ roff_unsupp, NULL, NULL, 0 },  /* return */
+	{ roff_return, NULL, NULL, 0 },  /* return */
 	{ roff_unsupp, NULL, NULL, 0 },  /* rfschar */
 	{ roff_line_ignore, NULL, NULL, 0 },  /* rhang */
 	{ roff_rm, NULL, NULL, 0 },  /* rm */
@@ -533,7 +548,7 @@ static	struct roffmac	 roffs[TOKEN_NONE]
 	{ roff_unsupp, NULL, NULL, 0 },  /* schar */
 	{ roff_line_ignore, NULL, NULL, 0 },  /* sentchar */
 	{ roff_line_ignore, NULL, NULL, 0 },  /* shc */
-	{ roff_unsupp, NULL, NULL, 0 },  /* shift */
+	{ roff_shift, NULL, NULL, 0 },  /* shift */
 	{ roff_line_ignore, NULL, NULL, 0 },  /* sizes */
 	{ roff_so, NULL, NULL, 0 },  /* so */
 	{ roff_line_ignore, NULL, NULL, 0 },  /* spacewidth */
@@ -713,6 +728,9 @@ roff_free1(struct roff *r)
 		eqn_free(r->last_eqn);
 	r->last_eqn = r->eqn = NULL;
 
+	while (r->mstackpos >= 0)
+		roff_userret(r);
+
 	while (r->last)
 		roffnode_pop(r);
 
@@ -752,7 +770,12 @@ roff_reset(struct roff *r)
 void
 roff_free(struct roff *r)
 {
+	int	 	 i;
+
 	roff_free1(r);
+	for (i = 0; i < r->mstacksz; i++)
+		free(r->mstack[i].argv);
+	free(r->mstack);
 	roffhash_free(r->reqtab);
 	free(r);
 }
@@ -767,6 +790,7 @@ roff_alloc(struct mparse *parse, int opt
 	r->reqtab = roffhash_alloc(0, ROFF_RENAMED);
 	r->options = options;
 	r->format = options & (MPARSE_MDOC | MPARSE_MAN);
+	r->mstackpos = -1;
 	r->rstackpos = -1;
 	r->escape = '\\';
 	return r;
@@ -1123,6 +1147,7 @@ deroff(char **dest, const struct roff_no
 static enum rofferr
 roff_res(struct roff *r, struct buf *buf, int ln, int pos)
 {
+	struct mctx	*ctx;	/* current macro call context */
 	char		 ubuf[24]; /* buffer to print the number */
 	struct roff_node *n;	/* used for header comments */
 	const char	*start;	/* start of the string to process */
@@ -1134,11 +1159,14 @@ roff_res(struct roff *r, struct buf *buf
 	char		*nbuf;	/* new buffer to copy buf->buf to */
 	size_t		 maxl;  /* expected length of the escape name */
 	size_t		 naml;	/* actual length of the escape name */
+	size_t		 asz;	/* length of the replacement */
+	size_t		 rsz;	/* length of the rest of the string */
 	enum mandoc_esc	 esc;	/* type of the escape sequence */
 	int		 inaml;	/* length returned from mandoc_escape() */
 	int		 expand_count;	/* to avoid infinite loops */
 	int		 npos;	/* position in numeric expression */
 	int		 arg_complete; /* argument not interrupted by eol */
+	int		 quote_args; /* true for \\$@, false for \\$* */
 	int		 done;	/* no more input available */
 	int		 deftype; /* type of definition to paste */
 	int		 rcsid;	/* kind of RCS id seen */
@@ -1275,6 +1303,7 @@ roff_res(struct roff *r, struct buf *buf
 		cp = stesc + 1;
 		switch (*cp) {
 		case '*':
+		case '$':
 			res = NULL;
 			break;
 		case 'B':
@@ -1391,6 +1420,62 @@ roff_res(struct roff *r, struct buf *buf
 				}
 			}
 			break;
+		case '$':
+			if (r->mstackpos < 0) {
+				mandoc_vmsg(MANDOCERR_ARG_UNDEF,
+				    r->parse, ln, (int)(stesc - buf->buf),
+				    "%.3s", stesc);
+				break;
+			}
+			ctx = r->mstack + r->mstackpos;
+			npos = stesc[2] - '1';
+			if (npos >= 0 && npos <= 8) {
+				res = npos < ctx->argc ?
+				    ctx->argv[npos] : "";
+				break;
+			}
+			if (stesc[2] == '*')
+				quote_args = 0;
+			else if (stesc[2] == '@')
+				quote_args = 1;
+			else {
+				mandoc_vmsg(MANDOCERR_ARG_NONUM,
+				    r->parse, ln, (int)(stesc - buf->buf),
+				    "%.3s", stesc);
+				break;
+			}
+			asz = 0;
+			for (npos = 0; npos < ctx->argc; npos++) {
+				if (npos)
+					asz++;  /* blank */
+				if (quote_args)
+					asz += 2;  /* quotes */
+				asz += strlen(ctx->argv[npos]);
+			}
+			if (asz != 3) {
+				rsz = buf->sz - (stesc - buf->buf) - 3;
+				if (asz < 3)
+					memmove(stesc + asz, stesc + 3, rsz);
+				buf->sz += asz - 3;
+				nbuf = mandoc_realloc(buf->buf, buf->sz);
+				start = nbuf + pos;
+				stesc = nbuf + (stesc - buf->buf);
+				buf->buf = nbuf;
+				if (asz > 3)
+					memmove(stesc + asz, stesc + 3, rsz);
+			}
+			for (npos = 0; npos < ctx->argc; npos++) {
+				if (npos)
+					*stesc++ = ' ';
+				if (quote_args)
+					*stesc++ = '"';
+				cp = ctx->argv[npos];
+				while (*cp != '\0')
+					*stesc++ = *cp++;
+				if (quote_args)
+					*stesc++ = '"';
+			}
+			continue;
 		case 'B':
 			npos = 0;
 			ubuf[0] = arg_complete &&
@@ -1414,9 +1499,10 @@ roff_res(struct roff *r, struct buf *buf
 		}
 
 		if (res == NULL) {
-			mandoc_vmsg(MANDOCERR_STR_UNDEF,
-			    r->parse, ln, (int)(stesc - buf->buf),
-			    "%.*s", (int)naml, stnam);
+			if (stesc[1] == '*')
+				mandoc_vmsg(MANDOCERR_STR_UNDEF,
+				    r->parse, ln, (int)(stesc - buf->buf),
+				    "%.*s", (int)naml, stnam);
 			res = "";
 		} else if (buf->sz + strlen(res) > SHRT_MAX) {
 			mandoc_msg(MANDOCERR_ROFFLOOP, r->parse,
@@ -1634,6 +1720,25 @@ roff_parseln(struct roff *r, int ln, str
 	return (*roffs[t].proc)(r, t, buf, ln, spos, pos, offs);
 }
 
+/*
+ * Internal interface function to tell the roff parser that execution
+ * of the current macro ended.  This is required because macro
+ * definitions usually do not end with a .return request.
+ */
+void
+roff_userret(struct roff *r)
+{
+	struct mctx	*ctx;
+	int		 i;
+
+	assert(r->mstackpos >= 0);
+	ctx = r->mstack + r->mstackpos;
+	for (i = 0; i < ctx->argc; i++)
+		free(ctx->argv[i]);
+	ctx->argc = 0;
+	r->mstackpos--;
+}
+
 void
 roff_endparse(struct roff *r)
 {
@@ -2662,7 +2767,7 @@ roff_getregro(const struct roff *r, cons
 
 	switch (*name) {
 	case '$':  /* Number of arguments of the last macro evaluated. */
-		return 0;
+		return r->mstackpos < 0 ? 0 : r->mstack[r->mstackpos].argc;
 	case 'A':  /* ASCII approximation mode is always off. */
 		return 0;
 	case 'g':  /* Groff compatibility mode is always on. */
@@ -3293,6 +3398,22 @@ roff_tr(ROFF_ARGS)
 	return ROFF_IGN;
 }
 
+/*
+ * Implementation of the .return request.
+ * There is no need to call roff_userret() from here.
+ * The read module will call that after rewinding the reader stack
+ * to the place from where the current macro was called.
+ */
+static enum rofferr
+roff_return(ROFF_ARGS)
+{
+	if (r->mstackpos >= 0)
+		return ROFF_USERRET;
+
+	mandoc_msg(MANDOCERR_REQ_NOMAC, r->parse, ln, ppos, "return");
+	return ROFF_IGN;
+}
+
 static enum rofferr
 roff_rn(ROFF_ARGS)
 {
@@ -3344,6 +3465,39 @@ roff_rn(ROFF_ARGS)
 }
 
 static enum rofferr
+roff_shift(ROFF_ARGS)
+{
+	struct mctx	*ctx;
+	int		 levels, i;
+
+	levels = 1;
+	if (buf->buf[pos] != '\0' &&
+	    roff_evalnum(r, ln, buf->buf, &pos, &levels, 0) == 0) {
+		mandoc_vmsg(MANDOCERR_CE_NONUM, r->parse,
+		    ln, pos, "shift %s", buf->buf + pos);
+		levels = 1;
+	}
+	if (r->mstackpos < 0) {
+		mandoc_msg(MANDOCERR_REQ_NOMAC, r->parse, ln, ppos, "shift");
+		return ROFF_IGN;
+	}
+	ctx = r->mstack + r->mstackpos;
+	if (levels > ctx->argc) {
+		mandoc_vmsg(MANDOCERR_SHIFT, r->parse,
+		    ln, pos, "%d, but max is %d", levels, ctx->argc);
+		levels = ctx->argc;
+	}
+	if (levels == 0)
+		return ROFF_IGN;
+	for (i = 0; i < levels; i++)
+		free(ctx->argv[i]);
+	ctx->argc -= levels;
+	for (i = 0; i < ctx->argc; i++)
+		ctx->argv[i] = ctx->argv[i + levels];
+	return ROFF_IGN;
+}
+
+static enum rofferr
 roff_so(ROFF_ARGS)
 {
 	char *name, *cp;
@@ -3378,186 +3532,58 @@ roff_so(ROFF_ARGS)
 static enum rofferr
 roff_userdef(ROFF_ARGS)
 {
-	const char	 *arg[16], *ap;
-	char		 *cp, *n1, *n2;
-	int		  argc, expand_count, i, ib, ie, quote_args;
-	size_t		  asz, esz, rsz;
-
-	/*
-	 * Collect pointers to macro argument strings
-	 * and NUL-terminate them.
-	 */
-
-	argc = 0;
-	cp = buf->buf + pos;
-	for (i = 0; i < 16; i++) {
-		if (*cp == '\0')
-			arg[i] = "";
-		else {
-			arg[i] = mandoc_getarg(r->parse, &cp, ln, &pos);
-			argc = i + 1;
-		}
-	}
-
-	/*
-	 * Expand macro arguments.
-	 */
-
-	buf->sz = strlen(r->current_string) + 1;
-	n1 = n2 = cp = mandoc_malloc(buf->sz);
-	memcpy(n1, r->current_string, buf->sz);
-	expand_count = 0;
-	while (*cp != '\0') {
-
-		/* Scan ahead for the next argument invocation. */
-
-		if (*cp++ != '\\')
-			continue;
-		if (*cp++ != '$')
-			continue;
-
-		quote_args = 0;
-		switch (*cp) {
-		case '@':  /* \\$@ inserts all arguments, quoted */
-			quote_args = 1;
-			/* FALLTHROUGH */
-		case '*':  /* \\$* inserts all arguments, unquoted */
-			ib = 0;
-			ie = argc - 1;
-			break;
-		default:  /* \\$1 .. \\$9 insert one argument */
-			ib = ie = *cp - '1';
-			if (ib < 0 || ib > 8)
-				continue;
-			break;
-		}
-		cp -= 2;
-
-		/*
-		 * Prevent infinite recursion.
-		 */
-
-		if (cp >= n2)
-			expand_count = 1;
-		else if (++expand_count > EXPAND_LIMIT) {
-			mandoc_msg(MANDOCERR_ROFFLOOP, r->parse,
-			    ln, (int)(cp - n1), NULL);
-			free(buf->buf);
-			buf->buf = n1;
-			*offs = 0;
-			return ROFF_IGN;
-		}
-
-		/*
-		 * Determine the size of the expanded argument,
-		 * taking escaping of quotes into account.
-		 */
-
-		asz = ie > ib ? ie - ib : 0;  /* for blanks */
-		for (i = ib; i <= ie; i++) {
-			if (quote_args)
-				asz += 2;
-			for (ap = arg[i]; *ap != '\0'; ap++) {
-				asz++;
-				if (*ap == '"')
-					asz += 3;
-			}
-		}
-		if (asz != 3) {
-
-			/*
-			 * Determine the size of the rest of the
-			 * unexpanded macro, including the NUL.
-			 */
-
-			rsz = buf->sz - (cp - n1) - 3;
-
-			/*
-			 * When shrinking, move before
-			 * releasing the storage.
-			 */
-
-			if (asz < 3)
-				memmove(cp + asz, cp + 3, rsz);
-
-			/*
-			 * Resize the storage for the macro
-			 * and readjust the parse pointer.
-			 */
-
-			buf->sz += asz - 3;
-			n2 = mandoc_realloc(n1, buf->sz);
-			cp = n2 + (cp - n1);
-			n1 = n2;
-
-			/*
-			 * When growing, make room
-			 * for the expanded argument.
-			 */
-
-			if (asz > 3)
-				memmove(cp + asz, cp + 3, rsz);
-		}
-
-		/* Copy the expanded argument, escaping quotes. */
-
-		n2 = cp;
-		for (i = ib; i <= ie; i++) {
-			if (quote_args)
-				*n2++ = '"';
-			for (ap = arg[i]; *ap != '\0'; ap++) {
-				if (*ap == '"') {
-					memcpy(n2, "\\(dq", 4);
-					n2 += 4;
-				} else
-					*n2++ = *ap;
-			}
-			if (quote_args)
-				*n2++ = '"';
-			if (i < ie)
-				*n2++ = ' ';
+	struct mctx	 *ctx;
+	char		 *arg, *ap, *dst, *src;
+	size_t		  sz;
+
+	/* Initialize a new macro stack context. */
+
+	if (++r->mstackpos == r->mstacksz) {
+		r->mstack = mandoc_recallocarray(r->mstack,
+		    r->mstacksz, r->mstacksz + 8, sizeof(*r->mstack));
+		r->mstacksz += 8;
+	}
+	ctx = r->mstack + r->mstackpos;
+	ctx->argsz = 0;
+	ctx->argc = 0;
+	ctx->argv = NULL;
+
+	/*
+	 * Collect pointers to macro argument strings,
+	 * NUL-terminating them and escaping quotes.
+	 */
+
+	src = buf->buf + pos;
+	while (*src != '\0') {
+		if (ctx->argc == ctx->argsz) {
+			ctx->argsz += 8;
+			ctx->argv = mandoc_reallocarray(ctx->argv,
+			    ctx->argsz, sizeof(*ctx->argv));
+		}
+		arg = mandoc_getarg(r->parse, &src, ln, &pos);
+		sz = 1;  /* For the terminating NUL. */
+		for (ap = arg; *ap != '\0'; ap++)
+			sz += *ap == '"' ? 4 : 1;
+		ctx->argv[ctx->argc++] = dst = mandoc_malloc(sz);
+		for (ap = arg; *ap != '\0'; ap++) {
+			if (*ap == '"') {
+				memcpy(dst, "\\(dq", 4);
+				dst += 4;
+			} else
+				*dst++ = *ap;
 		}
+		*dst = '\0';
 	}
 
-	/*
-	 * Expand the number of arguments, if it is used.
-	 * This never makes the expanded macro longer.
-	 */
-
-	for (cp = n1; *cp != '\0'; cp++) {
-		if (cp[0] != '\\')
-			continue;
-		if (cp[1] == '\\') {
-			cp++;
-			continue;
-		}
-		if (strncmp(cp + 1, "n(.$", 4) == 0)
-			esz = 5;
-		else if (strncmp(cp + 1, "n[.$]", 5) == 0)
-			esz = 6;
-		else
-			continue;
-		asz = snprintf(cp, esz, "%d", argc);
-		assert(asz < esz);
-		rsz = buf->sz - (cp - n1) - esz;
-		memmove(cp + asz, cp + esz, rsz);
-		buf->sz -= esz - asz;
-		n2 = mandoc_realloc(n1, buf->sz);
-		cp = n2 + (cp - n1) + asz;
-		n1 = n2;
-	}
-
-	/*
-	 * Replace the macro invocation
-	 * by the expanded macro.
-	 */
+	/* Replace the macro invocation by the macro definition. */
 
 	free(buf->buf);
-	buf->buf = n1;
+	buf->buf = mandoc_strdup(r->current_string);
+	buf->sz = strlen(buf->buf) + 1;
 	*offs = 0;
 
 	return buf->sz > 1 && buf->buf[buf->sz - 2] == '\n' ?
-	   ROFF_REPARSE : ROFF_APPEND;
+	   ROFF_USERCALL : ROFF_APPEND;
 }
 
 /*
Index: mandoc.h
===================================================================
RCS file: /home/cvs/mandoc/mandoc/mandoc.h,v
retrieving revision 1.249
retrieving revision 1.250
diff -Lmandoc.h -Lmandoc.h -u -p -r1.249 -r1.250
--- mandoc.h
+++ mandoc.h
@@ -195,6 +195,7 @@ enum	mandocerr {
 	MANDOCERR_ROFFLOOP, /* input stack limit exceeded, infinite loop? */
 	MANDOCERR_CHAR_BAD, /* skipping bad character: number */
 	MANDOCERR_MACRO, /* skipping unknown macro: macro */
+	MANDOCERR_REQ_NOMAC, /* skipping request outside macro: ... */
 	MANDOCERR_REQ_INSEC, /* skipping insecure request: request */
 	MANDOCERR_IT_STRAY, /* skipping item outside list: It ... */
 	MANDOCERR_TA_STRAY, /* skipping column outside column list: Ta */
@@ -205,6 +206,8 @@ enum	mandocerr {
 
 	/* related to request and macro arguments */
 	MANDOCERR_NAMESC, /* escaped character not allowed in a name: name */
+	MANDOCERR_ARG_UNDEF, /* using macro argument outside macro */
+	MANDOCERR_ARG_NONUM, /* argument number is not numeric */
 	MANDOCERR_BD_FILE, /* NOT IMPLEMENTED: Bd -file */
 	MANDOCERR_BD_NOARG, /* skipping display without arguments: Bd */
 	MANDOCERR_BL_NOTYPE, /* missing list type, using -item: Bl */
@@ -213,6 +216,7 @@ enum	mandocerr {
 	MANDOCERR_OS_UNAME, /* uname(3) system call failed, using UNKNOWN */
 	MANDOCERR_ST_BAD, /* unknown standard specifier: St standard */
 	MANDOCERR_IT_NONUM, /* skipping request without numeric argument */
+	MANDOCERR_SHIFT, /* excessive shift: ..., but max is ... */
 	MANDOCERR_SO_PATH, /* NOT IMPLEMENTED: .so with absolute path or ".." */
 	MANDOCERR_SO_FAIL, /* .so request failed */
 	MANDOCERR_ARG_SKIP, /* skipping all arguments: macro args */
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/roff/Makefile,v
retrieving revision 1.5
retrieving revision 1.6
diff -Lregress/roff/Makefile -Lregress/roff/Makefile -u -p -r1.5 -r1.6
--- regress/roff/Makefile
+++ regress/roff/Makefile
@@ -1,7 +1,7 @@
-# $OpenBSD: Makefile,v 1.20 2015/02/06 16:05:51 schwarze Exp $
+# $OpenBSD: Makefile,v 1.25 2018/08/23 14:16:12 schwarze Exp $
 
 SUBDIR  = args cond esc scale string
-SUBDIR += br cc de ds ft ig it ll na nr po ps rm rn sp ta ti tr
+SUBDIR += br cc de ds ft ig it ll na nr po ps return rm rn shift sp ta ti tr
 
 .include "../Makefile.sub"
 .include <bsd.subdir.mk>
--- /dev/null
+++ regress/roff/return/basic.out_lint
@@ -0,0 +1,3 @@
+mandoc: basic.in:10:2: ERROR: ignoring request outside macro: return
+mandoc: basic.in:18:32: ERROR: using macro argument outside macro: \$1
+mandoc: basic.in:21:2: ERROR: ignoring request outside macro: return
--- /dev/null
+++ regress/roff/return/basic.in
@@ -0,0 +1,23 @@
+.\" $OpenBSD: basic.in,v 1.1 2018/08/23 14:16:12 schwarze Exp $
+.Dd $Mdocdate: August 23 2018 $
+.Dt RETURN-BASIC 1
+.Os
+.Sh NAME
+.Nm return-basic
+.Nd the return request
+.Sh DESCRIPTION
+return before macro
+.return
+.Pp
+.de mymacro
+text from macro (\\n(.$ argument: "\\$1"),
+.return
+not printed,
+..
+.mymacro myarg
+\n(.$ arguments after return: "\$1",
+.Pp
+return after macro
+.return
+.Pp
+final text
--- /dev/null
+++ regress/roff/return/Makefile
@@ -0,0 +1,6 @@
+# $OpenBSD: Makefile,v 1.1 2018/08/23 14:16:12 schwarze Exp $
+
+REGRESS_TARGETS	= basic
+LINT_TARGETS	= basic
+
+.include <bsd.regress.mk>
--- /dev/null
+++ regress/roff/return/basic.out_ascii
@@ -0,0 +1,15 @@
+RETURN-BASIC(1)             General Commands Manual            RETURN-BASIC(1)
+
+N\bNA\bAM\bME\bE
+     r\bre\bet\btu\bur\brn\bn-\b-b\bba\bas\bsi\bic\bc - the return request
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     return before macro
+
+     text from macro (1 argument: "myarg"), 0 arguments after return: "",
+
+     return after macro
+
+     final text
+
+OpenBSD                         August 23, 2018                        OpenBSD
--- /dev/null
+++ regress/roff/shift/basic.in
@@ -0,0 +1,35 @@
+.\" $OpenBSD: basic.in,v 1.1 2018/08/23 14:16:12 schwarze Exp $
+.TH SHIFT_BASIC 1 "August 23, 2018"
+.SH NAME
+.B shift-basic
+\(en the shift request
+.SH DESCRIPTION
+.de showargs
+original arguments:
+.BI \\$@
+.PP
+.shift 2
+after shift 2:
+.BI \\$@
+.PP
+.shift
+after shift without argument:
+.BI \\$@
+.PP
+.shift 0
+after shift 0:
+.BI \\$@
+..
+.de useargs
+<\\$*>
+..
+.showargs one two three four five
+.PP
+expand to less than three bytes:
+.useargs 1
+.PP
+expand to exactly three bytes:
+.useargs x y
+.PP
+expand to more than three bytes:
+.useargs "a longer argument..." "and another"
--- /dev/null
+++ regress/roff/shift/bad.out_lint
@@ -0,0 +1,7 @@
+mandoc: bad.in:14:29: ERROR: using macro argument outside macro: \$1
+mandoc: bad.in:15:2: ERROR: ignoring request outside macro: shift
+mandoc: bad.in:17:31: ERROR: argument number is not numeric: \$x
+mandoc: bad.in:19:28: ERROR: using macro argument outside macro: \$1
+mandoc: bad.in:20:2: ERROR: ignoring request outside macro: shift
+mandoc: bad.in:28:8: ERROR: argument is not numeric, using 1: shift badarg
+mandoc: bad.in:28:9: ERROR: excessive shift: 2, but max is 1
--- /dev/null
+++ regress/roff/shift/bad.out_ascii
@@ -0,0 +1,25 @@
+SHIFT_BAD(1)                General Commands Manual               SHIFT_BAD(1)
+
+
+
+N\bNA\bAM\bME\bE
+       s\bsh\bhi\bif\bft\bt-\b-b\bba\bad\bd - wrong usage of macro arguments
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+       initial text
+
+       argument used before call: ""
+
+       in macro: "argument"
+
+       invalid argument number 'x': ""
+
+       argument used after call: ""
+
+       after shift badarg: "arg2" after excessive shift: 0 ""
+
+       final text
+
+
+
+OpenBSD                         August 23, 2018                   SHIFT_BAD(1)
--- /dev/null
+++ regress/roff/shift/Makefile
@@ -0,0 +1,6 @@
+# $OpenBSD: Makefile,v 1.1 2018/08/23 14:16:12 schwarze Exp $
+
+REGRESS_TARGETS	= basic bad
+LINT_TARGETS	= bad
+
+.include <bsd.regress.mk>
--- /dev/null
+++ regress/roff/shift/basic.out_ascii
@@ -0,0 +1,25 @@
+SHIFT_BASIC(1)              General Commands Manual             SHIFT_BASIC(1)
+
+
+
+N\bNA\bAM\bME\bE
+       s\bsh\bhi\bif\bft\bt-\b-b\bba\bas\bsi\bic\bc - the shift request
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+       original arguments: o\bon\bne\be_\bt_\bw_\bot\bth\bhr\bre\bee\be_\bf_\bo_\bu_\brf\bfi\biv\bve\be
+
+       after shift 2: t\bth\bhr\bre\bee\be_\bf_\bo_\bu_\brf\bfi\biv\bve\be
+
+       after shift without argument: f\bfo\bou\bur\br_\bf_\bi_\bv_\be
+
+       after shift 0: f\bfo\bou\bur\br_\bf_\bi_\bv_\be
+
+       expand to less than three bytes: <1>
+
+       expand to exactly three bytes: <x y>
+
+       expand to more than three bytes: <a longer argument... and another>
+
+
+
+OpenBSD                         August 23, 2018                 SHIFT_BASIC(1)
--- /dev/null
+++ regress/roff/shift/bad.in
@@ -0,0 +1,30 @@
+.\" $OpenBSD: bad.in,v 1.1 2018/08/23 14:16:12 schwarze Exp $
+.TH SHIFT_BAD 1 "August 23, 2018"
+.SH NAME
+.B shift-bad
+\(en wrong usage of macro arguments
+.SH DESCRIPTION
+initial text
+.de mym
+in macro: "\\$1"
+.PP
+invalid argument number 'x': "\\$x"
+..
+.PP
+argument used before call: "\$1"
+.shift
+.PP
+.mym argument
+.PP
+argument used after call: "\$1"
+.shift 2
+.PP
+.de mym
+.shift badarg
+after shift badarg: "\\$1"
+.shift 2
+after excessive shift: \\n(.$ "\\$1"
+..
+.mym arg1 arg2
+.PP
+final text
--
 To unsubscribe send an email to source+unsubscribe@mandoc.bsd.lv

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2018-08-23 14:30 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-08-23 14:30 mandoc: Implement the roff(7) .shift and .return requests, for example schwarze

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).