Merge from vendor branch BINUTILS:
[dragonfly.git] / contrib / nvi / vi / v_increment.c
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[] = "@(#)v_increment.c 10.12 (Berkeley) 3/19/96";
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 * const fmt[] = {
32 #define DEC     0
33         "%ld",
34 #define SDEC    1
35         "%+ld",
36 #define HEXC    2
37         "0X%0*lX",
38 #define HEXL    3
39         "0x%0*lx",
40 #define OCTAL   4
41         "%#0*lo",
42 };
43
44 static void inc_err __P((SCR *, enum nresult));
45
46 /*
47  * v_increment -- [count]#[#+-]
48  *      Increment/decrement a keyword number.
49  *
50  * PUBLIC: int v_increment __P((SCR *, VICMD *));
51  */
52 int
53 v_increment(sp, vp)
54         SCR *sp;
55         VICMD *vp;
56 {
57         enum nresult nret;
58         u_long ulval;
59         long change, ltmp, lval;
60         size_t beg, blen, end, len, nlen, wlen;
61         int base, isempty, rval;
62         char *bp, *ntype, *p, *t, nbuf[100];
63
64         /* Validate the operator. */
65         if (vp->character == '#')
66                 vp->character = '+';
67         if (vp->character != '+' && vp->character != '-') {
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(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  ishex
103 #define ishex(c)        (isdigit(c) || strchr("abcdefABCDEF", c))
104 #undef  isoctal
105 #define isoctal(c)      (isdigit(c) && (c) != '8' && (c) != '9')
106
107         /*
108          * Look for 0[Xx], or leading + or - signs, guess at the base.
109          * The character after that must be a number.  Wlen is set to
110          * the remaining characters in the line that could be part of
111          * the number.
112          */
113         wlen = len - beg;
114         if (p[beg] == '0' && wlen > 2 &&
115             (p[beg + 1] == 'X' || p[beg + 1] == 'x')) {
116                 base = 16;
117                 end = beg + 2;
118                 if (!ishex(p[end]))
119                         goto decimal;
120                 ntype = p[beg + 1] == 'X' ? fmt[HEXC] : fmt[HEXL];
121         } else if (p[beg] == '0' && wlen > 1) {
122                 base = 8;
123                 end = beg + 1;
124                 if (!isoctal(p[end]))
125                         goto decimal;
126                 ntype = fmt[OCTAL];
127         } else if (wlen >= 1 && (p[beg] == '+' || p[beg] == '-')) {
128                 base = 10;
129                 end = beg + 1;
130                 ntype = fmt[SDEC];
131                 if (!isdigit(p[end]))
132                         goto nonum;
133         } else {
134 decimal:        base = 10;
135                 end = beg;
136                 ntype = fmt[DEC];
137                 if (!isdigit(p[end])) {
138 nonum:                  msgq(sp, M_ERR, "181|Cursor not in a number");
139                         return (1);
140                 }
141         }
142
143         /* Find the end of the word, possibly correcting the base. */
144         while (++end < len) {
145                 switch (base) {
146                 case 8:
147                         if (isoctal(p[end]))
148                                 continue;
149                         if (p[end] == '8' || p[end] == '9') {
150                                 base = 10;
151                                 ntype = fmt[DEC];
152                                 continue;
153                         }
154                         break;
155                 case 10:
156                         if (isdigit(p[end]))
157                                 continue;
158                         break;
159                 case 16:
160                         if (ishex(p[end]))
161                                 continue;
162                         break;
163                 default:
164                         abort();
165                         /* NOTREACHED */
166                 }
167                 break;
168         }
169         wlen = (end - beg);
170
171         /*
172          * XXX
173          * If the line was at the end of the buffer, we have to copy it
174          * so we can guarantee that it's NULL-terminated.  We make the
175          * buffer big enough to fit the line changes as well, and only
176          * allocate once.
177          */
178         GET_SPACE_RET(sp, bp, blen, len + 50);
179         if (end == len) {
180                 memmove(bp, &p[beg], wlen);
181                 bp[wlen] = '\0';
182                 t = bp;
183         } else
184                 t = &p[beg];
185
186         /*
187          * Octal or hex deal in unsigned longs, everything else is done
188          * in signed longs.
189          */
190         if (base == 10) {
191                 if ((nret = nget_slong(&lval, t, NULL, 10)) != NUM_OK)
192                         goto err;
193                 ltmp = vp->character == '-' ? -change : change;
194                 if (lval > 0 && ltmp > 0 && !NPFITS(LONG_MAX, lval, ltmp)) {
195                         nret = NUM_OVER;
196                         goto err;
197                 }
198                 if (lval < 0 && ltmp < 0 && !NNFITS(LONG_MIN, lval, ltmp)) {
199                         nret = NUM_UNDER;
200                         goto err;
201                 }
202                 lval += ltmp;
203                 /* If we cross 0, signed numbers lose their sign. */
204                 if (lval == 0 && ntype == fmt[SDEC])
205                         ntype = fmt[DEC];
206                 nlen = snprintf(nbuf, sizeof(nbuf), ntype, lval);
207         } else {
208                 if ((nret = nget_uslong(&ulval, t, NULL, base)) != NUM_OK)
209                         goto err;
210                 if (vp->character == '+') {
211                         if (!NPFITS(ULONG_MAX, ulval, change)) {
212                                 nret = NUM_OVER;
213                                 goto err;
214                         }
215                         ulval += change;
216                 } else {
217                         if (ulval < change) {
218                                 nret = NUM_UNDER;
219                                 goto err;
220                         }
221                         ulval -= change;
222                 }
223
224                 /* Correct for literal "0[Xx]" in format. */
225                 if (base == 16)
226                         wlen -= 2;
227
228                 nlen = snprintf(nbuf, sizeof(nbuf), ntype, wlen, ulval);
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(sp, nret)
251         SCR *sp;
252         enum nresult nret;
253 {
254         switch (nret) {
255         case NUM_ERR:
256                 break;
257         case NUM_OK:
258                 abort();
259                 /* NOREACHED */
260         case NUM_OVER:
261                 msgq(sp, M_ERR, "182|Resulting number too large");
262                 break;
263         case NUM_UNDER:
264                 msgq(sp, M_ERR, "183|Resulting number too small");
265                 break;
266         }
267 }