xref: /openbsd/usr.bin/vi/vi/v_increment.c (revision a0b15055)
1 /*	$OpenBSD: v_increment.c,v 1.9 2016/01/06 22:28:52 millert Exp $	*/
2 
3 /*-
4  * Copyright (c) 1992, 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  * Copyright (c) 1992, 1993, 1994, 1995, 1996
7  *	Keith Bostic.  All rights reserved.
8  *
9  * See the LICENSE file for redistribution information.
10  */
11 
12 #include "config.h"
13 
14 #include <sys/types.h>
15 #include <sys/queue.h>
16 #include <sys/time.h>
17 
18 #include <bitstring.h>
19 #include <ctype.h>
20 #include <errno.h>
21 #include <limits.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "../common/common.h"
27 #include "vi.h"
28 
29 static char * const fmt[] = {
30 #define	DEC	0
31 	"%ld",
32 #define	SDEC	1
33 	"%+ld",
34 #define	HEXC	2
35 	"0X%0*lX",
36 #define	HEXL	3
37 	"0x%0*lx",
38 #define	OCTAL	4
39 	"%#0*lo",
40 };
41 
42 static void inc_err(SCR *, enum nresult);
43 
44 /*
45  * v_increment -- [count]#[#+-]
46  *	Increment/decrement a keyword number.
47  *
48  * PUBLIC: int v_increment(SCR *, VICMD *);
49  */
50 int
v_increment(SCR * sp,VICMD * vp)51 v_increment(SCR *sp, VICMD *vp)
52 {
53 	enum nresult nret;
54 	u_long ulval;
55 	long change, ltmp, lval;
56 	size_t beg, blen, end, len, nlen, wlen;
57 	int base, isempty, rval;
58 	char *bp, *ntype, *p, *t, nbuf[100];
59 
60 	/* Validate the operator. */
61 	if (vp->character == '#')
62 		vp->character = '+';
63 	if (vp->character != '+' && vp->character != '-') {
64 		v_emsg(sp, vp->kp->usage, VIM_USAGE);
65 		return (1);
66 	}
67 
68 	/* If new value set, save it off, but it has to fit in a long. */
69 	if (F_ISSET(vp, VC_C1SET)) {
70 		if (vp->count > LONG_MAX) {
71 			inc_err(sp, NUM_OVER);
72 			return (1);
73 		}
74 		change = vp->count;
75 	} else
76 		change = 1;
77 
78 	/* Get the line. */
79 	if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
80 		if (isempty)
81 			goto nonum;
82 		return (1);
83 	}
84 
85 	/*
86 	 * Skip any leading space before the number.  Getting a cursor word
87 	 * implies moving the cursor to its beginning, if we moved, refresh
88 	 * now.
89 	 */
90 	for (beg = vp->m_start.cno; beg < len && isspace(p[beg]); ++beg);
91 	if (beg >= len)
92 		goto nonum;
93 	if (beg != vp->m_start.cno) {
94 		sp->cno = beg;
95 		(void)vs_refresh(sp, 0);
96 	}
97 
98 #undef	ishex
99 #define	ishex(c)	(isdigit(c) || strchr("abcdefABCDEF", (c)))
100 #undef	isoctal
101 #define	isoctal(c)	(isdigit(c) && (c) != '8' && (c) != '9')
102 
103 	/*
104 	 * Look for 0[Xx], or leading + or - signs, guess at the base.
105 	 * The character after that must be a number.  Wlen is set to
106 	 * the remaining characters in the line that could be part of
107 	 * the number.
108 	 */
109 	wlen = len - beg;
110 	if (p[beg] == '0' && wlen > 2 &&
111 	    (p[beg + 1] == 'X' || p[beg + 1] == 'x')) {
112 		base = 16;
113 		end = beg + 2;
114 		if (!ishex(p[end]))
115 			goto decimal;
116 		ntype = p[beg + 1] == 'X' ? fmt[HEXC] : fmt[HEXL];
117 	} else if (p[beg] == '0' && wlen > 1) {
118 		base = 8;
119 		end = beg + 1;
120 		if (!isoctal(p[end]))
121 			goto decimal;
122 		ntype = fmt[OCTAL];
123 	} else if (wlen >= 1 && (p[beg] == '+' || p[beg] == '-')) {
124 		base = 10;
125 		end = beg + 1;
126 		ntype = fmt[SDEC];
127 		if (!isdigit(p[end]))
128 			goto nonum;
129 	} else {
130 decimal:	base = 10;
131 		end = beg;
132 		ntype = fmt[DEC];
133 		if (!isdigit(p[end])) {
134 nonum:			msgq(sp, M_ERR, "Cursor not in a number");
135 			return (1);
136 		}
137 	}
138 
139 	/* Find the end of the word, possibly correcting the base. */
140 	while (++end < len) {
141 		switch (base) {
142 		case 8:
143 			if (isoctal(p[end]))
144 				continue;
145 			if (p[end] == '8' || p[end] == '9') {
146 				base = 10;
147 				ntype = fmt[DEC];
148 				continue;
149 			}
150 			break;
151 		case 10:
152 			if (isdigit(p[end]))
153 				continue;
154 			break;
155 		case 16:
156 			if (ishex(p[end]))
157 				continue;
158 			break;
159 		default:
160 			abort();
161 			/* NOTREACHED */
162 		}
163 		break;
164 	}
165 	wlen = (end - beg);
166 
167 	/*
168 	 * XXX
169 	 * If the line was at the end of the buffer, we have to copy it
170 	 * so we can guarantee that it's NULL-terminated.  We make the
171 	 * buffer big enough to fit the line changes as well, and only
172 	 * allocate once.
173 	 */
174 	GET_SPACE_RET(sp, bp, blen, len + 50);
175 	if (end == len) {
176 		memmove(bp, &p[beg], wlen);
177 		bp[wlen] = '\0';
178 		t = bp;
179 	} else
180 		t = &p[beg];
181 
182 	/*
183 	 * Octal or hex deal in unsigned longs, everything else is done
184 	 * in signed longs.
185 	 */
186 	if (base == 10) {
187 		if ((nret = nget_slong(&lval, t, NULL, 10)) != NUM_OK)
188 			goto err;
189 		ltmp = vp->character == '-' ? -change : change;
190 		if (lval > 0 && ltmp > 0 && !NPFITS(LONG_MAX, lval, ltmp)) {
191 			nret = NUM_OVER;
192 			goto err;
193 		}
194 		if (lval < 0 && ltmp < 0 && !NNFITS(LONG_MIN, lval, ltmp)) {
195 			nret = NUM_UNDER;
196 			goto err;
197 		}
198 		lval += ltmp;
199 		/* If we cross 0, signed numbers lose their sign. */
200 		if (lval == 0 && ntype == fmt[SDEC])
201 			ntype = fmt[DEC];
202 		nlen = snprintf(nbuf, sizeof(nbuf), ntype, lval);
203 		if (nlen >= sizeof(nbuf))
204 			nlen = sizeof(nbuf) - 1;
205 	} else {
206 		if ((nret = nget_uslong(&ulval, t, NULL, base)) != NUM_OK)
207 			goto err;
208 		if (vp->character == '+') {
209 			if (!NPFITS(ULONG_MAX, ulval, change)) {
210 				nret = NUM_OVER;
211 				goto err;
212 			}
213 			ulval += change;
214 		} else {
215 			if (ulval < change) {
216 				nret = NUM_UNDER;
217 				goto err;
218 			}
219 			ulval -= change;
220 		}
221 
222 		/* Correct for literal "0[Xx]" in format. */
223 		if (base == 16)
224 			wlen -= 2;
225 
226 		nlen = snprintf(nbuf, sizeof(nbuf), ntype, wlen, ulval);
227 		if (nlen >= sizeof(nbuf))
228 			nlen = sizeof(nbuf) - 1;
229 	}
230 
231 	/* Build the new line. */
232 	memmove(bp, p, beg);
233 	memmove(bp + beg, nbuf, nlen);
234 	memmove(bp + beg + nlen, p + end, len - beg - (end - beg));
235 	len = beg + nlen + (len - beg - (end - beg));
236 
237 	nret = NUM_OK;
238 	rval = db_set(sp, vp->m_start.lno, bp, len);
239 
240 	if (0) {
241 err:		rval = 1;
242 		inc_err(sp, nret);
243 	}
244 	if (bp != NULL)
245 		FREE_SPACE(sp, bp, blen);
246 	return (rval);
247 }
248 
249 static void
inc_err(SCR * sp,enum nresult nret)250 inc_err(SCR *sp, enum nresult nret)
251 {
252 	switch (nret) {
253 	case NUM_ERR:
254 		break;
255 	case NUM_OK:
256 		abort();
257 		/* NOREACHED */
258 	case NUM_OVER:
259 		msgq(sp, M_ERR, "Resulting number too large");
260 		break;
261 	case NUM_UNDER:
262 		msgq(sp, M_ERR, "Resulting number too small");
263 		break;
264 	}
265 }
266