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