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