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 |
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 |
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 |
|
|
} |