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[] = "$Id: v_increment.c,v 10.17 2011/12/02 01:17:53 zy Exp $"; 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_T * const fmt[] = { 32 #define DEC 0 33 L("%ld"), 34 #define SDEC 1 35 L("%+ld"), 36 #define HEXC 2 37 L("0X%0*lX"), 38 #define HEXL 3 39 L("0x%0*lx"), 40 #define OCTAL 4 41 L("%#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(SCR *sp, VICMD *vp) 54 { 55 enum nresult nret; 56 u_long ulval; 57 long change, ltmp, lval; 58 size_t beg, blen, end, len, nlen, wlen; 59 int base, isempty, rval; 60 CHAR_T *ntype, nbuf[100]; 61 CHAR_T *bp, *p, *t; 62 63 /* Validate the operator. */ 64 if (vp->character == '#') 65 vp->character = '+'; 66 if (vp->character != '+' && vp->character != '-') { 67 v_emsg(sp, vp->kp->usage, VIM_USAGE); 68 return (1); 69 } 70 71 /* If new value set, save it off, but it has to fit in a long. */ 72 if (F_ISSET(vp, VC_C1SET)) { 73 if (vp->count > LONG_MAX) { 74 inc_err(sp, NUM_OVER); 75 return (1); 76 } 77 change = vp->count; 78 } else 79 change = 1; 80 81 /* Get the line. */ 82 if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { 83 if (isempty) 84 goto nonum; 85 return (1); 86 } 87 88 /* 89 * Skip any leading space before the number. Getting a cursor word 90 * implies moving the cursor to its beginning, if we moved, refresh 91 * now. 92 */ 93 for (beg = vp->m_start.cno; beg < len && ISSPACE(p[beg]); ++beg); 94 if (beg >= len) 95 goto nonum; 96 if (beg != vp->m_start.cno) { 97 sp->cno = beg; 98 (void)vs_refresh(sp, 0); 99 } 100 101 #undef ishex 102 #define ishex(c) (ISXDIGIT(c)) 103 #undef isoctal 104 #define isoctal(c) ((c) >= '0' && (c) <= '7') 105 106 /* 107 * Look for 0[Xx], or leading + or - signs, guess at the base. 108 * The character after that must be a number. Wlen is set to 109 * the remaining characters in the line that could be part of 110 * the number. 111 */ 112 wlen = len - beg; 113 if (p[beg] == '0' && wlen > 2 && 114 (p[beg + 1] == 'X' || p[beg + 1] == 'x')) { 115 base = 16; 116 end = beg + 2; 117 if (!ishex(p[end])) 118 goto decimal; 119 ntype = p[beg + 1] == 'X' ? fmt[HEXC] : fmt[HEXL]; 120 } else if (p[beg] == '0' && wlen > 1) { 121 base = 8; 122 end = beg + 1; 123 if (!isoctal(p[end])) 124 goto decimal; 125 ntype = fmt[OCTAL]; 126 } else if (wlen >= 1 && (p[beg] == '+' || p[beg] == '-')) { 127 base = 10; 128 end = beg + 1; 129 ntype = fmt[SDEC]; 130 if (!isdigit(p[end])) 131 goto nonum; 132 } else { 133 decimal: base = 10; 134 end = beg; 135 ntype = fmt[DEC]; 136 if (!isdigit(p[end])) { 137 nonum: msgq(sp, M_ERR, "181|Cursor not in a number"); 138 return (1); 139 } 140 } 141 142 /* Find the end of the word, possibly correcting the base. */ 143 while (++end < len) { 144 switch (base) { 145 case 8: 146 if (isoctal(p[end])) 147 continue; 148 if (p[end] == '8' || p[end] == '9') { 149 base = 10; 150 ntype = fmt[DEC]; 151 continue; 152 } 153 break; 154 case 10: 155 if (isdigit(p[end])) 156 continue; 157 break; 158 case 16: 159 if (ishex(p[end])) 160 continue; 161 break; 162 default: 163 abort(); 164 /* NOTREACHED */ 165 } 166 break; 167 } 168 wlen = (end - beg); 169 170 /* 171 * XXX 172 * If the line was at the end of the buffer, we have to copy it 173 * so we can guarantee that it's NULL-terminated. We make the 174 * buffer big enough to fit the line changes as well, and only 175 * allocate once. 176 */ 177 GET_SPACE_RETW(sp, bp, blen, len + 50); 178 if (end == len) { 179 MEMMOVE(bp, &p[beg], wlen); 180 bp[wlen] = '\0'; 181 t = bp; 182 } else 183 t = &p[beg]; 184 185 /* 186 * Octal or hex deal in unsigned longs, everything else is done 187 * in signed longs. 188 */ 189 if (base == 10) { 190 if ((nret = nget_slong(&lval, t, NULL, 10)) != NUM_OK) 191 goto err; 192 ltmp = vp->character == '-' ? -change : change; 193 if (lval > 0 && ltmp > 0 && !NPFITS(LONG_MAX, lval, 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, sizeof(nbuf), ntype, lval); 206 } else { 207 if ((nret = nget_uslong(&ulval, t, NULL, base)) != NUM_OK) 208 goto err; 209 if (vp->character == '+') { 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, sizeof(nbuf), ntype, wlen, ulval); 228 } 229 230 /* Build the new line. */ 231 MEMMOVE(bp, p, beg); 232 MEMMOVE(bp + beg, nbuf, nlen); 233 MEMMOVE(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