xref: /netbsd-src/external/bsd/nvi/dist/vi/v_increment.c (revision aceb213538ec08a74028e213127af18aa17bf1cf)
1 /*	$NetBSD: v_increment.c,v 1.2 2013/11/22 15:52:06 christos Exp $ */
2 /*-
3  * Copyright (c) 1992, 1993, 1994
4  *	The Regents of the University of California.  All rights reserved.
5  * Copyright (c) 1992, 1993, 1994, 1995, 1996
6  *	Keith Bostic.  All rights reserved.
7  *
8  * See the LICENSE file for redistribution information.
9  */
10 
11 #include "config.h"
12 
13 #ifndef lint
14 static const char sccsid[] = "Id: v_increment.c,v 10.16 2001/06/25 15:19:31 skimo Exp  (Berkeley) Date: 2001/06/25 15:19:31 ";
15 #endif /* not lint */
16 
17 #include <sys/types.h>
18 #include <sys/queue.h>
19 #include <sys/time.h>
20 
21 #include <bitstring.h>
22 #include <ctype.h>
23 #include <errno.h>
24 #include <limits.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include "../common/common.h"
30 #include "vi.h"
31 
32 static const CHAR_T *fmt[] = {
33 #define	DEC	0
34 	L("%ld"),
35 #define	SDEC	1
36 	L("%+ld"),
37 #define	HEXC	2
38 	L("0X%0*lX"),
39 #define	HEXL	3
40 	L("0x%0*lx"),
41 #define	OCTAL	4
42 	L("%#0*lo"),
43 };
44 
45 static void inc_err __P((SCR *, enum nresult));
46 
47 /*
48  * v_increment -- [count]#[#+-]
49  *	Increment/decrement a keyword number.
50  *
51  * PUBLIC: int v_increment __P((SCR *, VICMD *));
52  */
53 int
54 v_increment(SCR *sp, VICMD *vp)
55 {
56 	enum nresult nret;
57 	u_long ulval, change;
58 	long ltmp, lval;
59 	size_t beg, blen, end, len, nlen, wlen;
60 	int base, isempty, rval;
61 	CHAR_T *bp, *p, *t, nbuf[100];
62 	const CHAR_T *ntype;
63 
64 	/* Validate the operator. */
65 	if (vp->character == L('#'))
66 		vp->character = L('+');
67 	if (vp->character != L('+') && vp->character != L('-')) {
68 		v_emsg(sp, vp->kp->usage, VIM_USAGE);
69 		return (1);
70 	}
71 
72 	/* If new value set, save it off, but it has to fit in a long. */
73 	if (F_ISSET(vp, VC_C1SET)) {
74 		if (vp->count > LONG_MAX) {
75 			inc_err(sp, NUM_OVER);
76 			return (1);
77 		}
78 		change = vp->count;
79 	} else
80 		change = 1;
81 
82 	/* Get the line. */
83 	if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) {
84 		if (isempty)
85 			goto nonum;
86 		return (1);
87 	}
88 
89 	/*
90 	 * Skip any leading space before the number.  Getting a cursor word
91 	 * implies moving the cursor to its beginning, if we moved, refresh
92 	 * now.
93 	 */
94 	for (beg = vp->m_start.cno; beg < len && ISSPACE((UCHAR_T)p[beg]); ++beg);
95 	if (beg >= len)
96 		goto nonum;
97 	if (beg != vp->m_start.cno) {
98 		sp->cno = beg;
99 		(void)vs_refresh(sp, 0);
100 	}
101 
102 #undef	isoctal
103 #define	isoctal(c)	(ISDIGIT(c) && (c) != L('8') && (c) != L('9'))
104 
105 	/*
106 	 * Look for 0[Xx], or leading + or - signs, guess at the base.
107 	 * The character after that must be a number.  Wlen is set to
108 	 * the remaining characters in the line that could be part of
109 	 * the number.
110 	 */
111 	wlen = len - beg;
112 	if (p[beg] == L('0') && wlen > 2 &&
113 	    (p[beg + 1] == L('X') || p[beg + 1] == L('x'))) {
114 		base = 16;
115 		end = beg + 2;
116 		if (!ISXDIGIT((UCHAR_T)p[end]))
117 			goto decimal;
118 		ntype = p[beg + 1] == L('X') ? fmt[HEXC] : fmt[HEXL];
119 	} else if (p[beg] == L('0') && wlen > 1) {
120 		base = 8;
121 		end = beg + 1;
122 		if (!isoctal((UCHAR_T)p[end]))
123 			goto decimal;
124 		ntype = fmt[OCTAL];
125 	} else if (wlen >= 1 && (p[beg] == L('+') || p[beg] == L('-'))) {
126 		base = 10;
127 		end = beg + 1;
128 		ntype = fmt[SDEC];
129 		if (!ISDIGIT((UCHAR_T)p[end]))
130 			goto nonum;
131 	} else {
132 decimal:	base = 10;
133 		end = beg;
134 		ntype = fmt[DEC];
135 		if (!ISDIGIT(p[end])) {
136 nonum:			msgq(sp, M_ERR, "181|Cursor not in a number");
137 			return (1);
138 		}
139 	}
140 
141 	/* Find the end of the word, possibly correcting the base. */
142 	while (++end < len) {
143 		switch (base) {
144 		case 8:
145 			if (isoctal((UCHAR_T)p[end]))
146 				continue;
147 			if (p[end] == L('8') || p[end] == L('9')) {
148 				base = 10;
149 				ntype = fmt[DEC];
150 				continue;
151 			}
152 			break;
153 		case 10:
154 			if (ISDIGIT((UCHAR_T)p[end]))
155 				continue;
156 			break;
157 		case 16:
158 			if (ISXDIGIT((UCHAR_T)p[end]))
159 				continue;
160 			break;
161 		default:
162 			abort();
163 			/* NOTREACHED */
164 		}
165 		break;
166 	}
167 	wlen = (end - beg);
168 
169 	/*
170 	 * XXX
171 	 * If the line was at the end of the buffer, we have to copy it
172 	 * so we can guarantee that it's NULL-terminated.  We make the
173 	 * buffer big enough to fit the line changes as well, and only
174 	 * allocate once.
175 	 */
176 	GET_SPACE_RETW(sp, bp, blen, len + 50);
177 	if (end == len) {
178 		MEMMOVEW(bp, &p[beg], wlen);
179 		bp[wlen] = L('\0');
180 		t = bp;
181 	} else
182 		t = &p[beg];
183 
184 	/*
185 	 * Octal or hex deal in unsigned longs, everything else is done
186 	 * in signed longs.
187 	 */
188 	if (base == 10) {
189 		if ((nret = nget_slong(sp, &lval, t, NULL, 10)) != NUM_OK)
190 			goto err;
191 		ltmp = vp->character == L('-') ? -change : change;
192 		if (lval > 0 && ltmp > 0 &&
193 		    !NPFITS(LONG_MAX, (unsigned long)lval, (unsigned long)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, SIZE(nbuf), ntype, lval);
206 	} else {
207 		if ((nret = nget_uslong(sp, &ulval, t, NULL, base)) != NUM_OK)
208 			goto err;
209 		if (vp->character == L('+')) {
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, SIZE(nbuf), ntype, wlen, ulval);
228 	}
229 
230 	/* Build the new line. */
231 	MEMMOVEW(bp, p, beg);
232 	MEMMOVEW(bp + beg, nbuf, nlen);
233 	MEMMOVEW(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