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