diff options
Diffstat (limited to 'vi/v_increment.c')
-rw-r--r-- | vi/v_increment.c | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/vi/v_increment.c b/vi/v_increment.c new file mode 100644 index 000000000000..45e763fa5bd5 --- /dev/null +++ b/vi/v_increment.c @@ -0,0 +1,267 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_increment.c 10.12 (Berkeley) 3/19/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +static char * const fmt[] = { +#define DEC 0 + "%ld", +#define SDEC 1 + "%+ld", +#define HEXC 2 + "0X%0*lX", +#define HEXL 3 + "0x%0*lx", +#define OCTAL 4 + "%#0*lo", +}; + +static void inc_err __P((SCR *, enum nresult)); + +/* + * v_increment -- [count]#[#+-] + * Increment/decrement a keyword number. + * + * PUBLIC: int v_increment __P((SCR *, VICMD *)); + */ +int +v_increment(sp, vp) + SCR *sp; + VICMD *vp; +{ + enum nresult nret; + u_long ulval; + long change, ltmp, lval; + size_t beg, blen, end, len, nlen, wlen; + int base, isempty, rval; + char *bp, *ntype, *p, *t, nbuf[100]; + + /* Validate the operator. */ + if (vp->character == '#') + vp->character = '+'; + if (vp->character != '+' && vp->character != '-') { + v_emsg(sp, vp->kp->usage, VIM_USAGE); + return (1); + } + + /* If new value set, save it off, but it has to fit in a long. */ + if (F_ISSET(vp, VC_C1SET)) { + if (vp->count > LONG_MAX) { + inc_err(sp, NUM_OVER); + return (1); + } + change = vp->count; + } else + change = 1; + + /* Get the line. */ + if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { + if (isempty) + goto nonum; + return (1); + } + + /* + * Skip any leading space before the number. Getting a cursor word + * implies moving the cursor to its beginning, if we moved, refresh + * now. + */ + for (beg = vp->m_start.cno; beg < len && isspace(p[beg]); ++beg); + if (beg >= len) + goto nonum; + if (beg != vp->m_start.cno) { + sp->cno = beg; + (void)vs_refresh(sp, 0); + } + +#undef ishex +#define ishex(c) (isdigit(c) || strchr("abcdefABCDEF", c)) +#undef isoctal +#define isoctal(c) (isdigit(c) && (c) != '8' && (c) != '9') + + /* + * Look for 0[Xx], or leading + or - signs, guess at the base. + * The character after that must be a number. Wlen is set to + * the remaining characters in the line that could be part of + * the number. + */ + wlen = len - beg; + if (p[beg] == '0' && wlen > 2 && + (p[beg + 1] == 'X' || p[beg + 1] == 'x')) { + base = 16; + end = beg + 2; + if (!ishex(p[end])) + goto decimal; + ntype = p[beg + 1] == 'X' ? fmt[HEXC] : fmt[HEXL]; + } else if (p[beg] == '0' && wlen > 1) { + base = 8; + end = beg + 1; + if (!isoctal(p[end])) + goto decimal; + ntype = fmt[OCTAL]; + } else if (wlen >= 1 && (p[beg] == '+' || p[beg] == '-')) { + base = 10; + end = beg + 1; + ntype = fmt[SDEC]; + if (!isdigit(p[end])) + goto nonum; + } else { +decimal: base = 10; + end = beg; + ntype = fmt[DEC]; + if (!isdigit(p[end])) { +nonum: msgq(sp, M_ERR, "181|Cursor not in a number"); + return (1); + } + } + + /* Find the end of the word, possibly correcting the base. */ + while (++end < len) { + switch (base) { + case 8: + if (isoctal(p[end])) + continue; + if (p[end] == '8' || p[end] == '9') { + base = 10; + ntype = fmt[DEC]; + continue; + } + break; + case 10: + if (isdigit(p[end])) + continue; + break; + case 16: + if (ishex(p[end])) + continue; + break; + default: + abort(); + /* NOTREACHED */ + } + break; + } + wlen = (end - beg); + + /* + * XXX + * If the line was at the end of the buffer, we have to copy it + * so we can guarantee that it's NULL-terminated. We make the + * buffer big enough to fit the line changes as well, and only + * allocate once. + */ + GET_SPACE_RET(sp, bp, blen, len + 50); + if (end == len) { + memmove(bp, &p[beg], wlen); + bp[wlen] = '\0'; + t = bp; + } else + t = &p[beg]; + + /* + * Octal or hex deal in unsigned longs, everything else is done + * in signed longs. + */ + if (base == 10) { + if ((nret = nget_slong(&lval, t, NULL, 10)) != NUM_OK) + goto err; + ltmp = vp->character == '-' ? -change : change; + if (lval > 0 && ltmp > 0 && !NPFITS(LONG_MAX, lval, ltmp)) { + nret = NUM_OVER; + goto err; + } + if (lval < 0 && ltmp < 0 && !NNFITS(LONG_MIN, lval, ltmp)) { + nret = NUM_UNDER; + goto err; + } + lval += ltmp; + /* If we cross 0, signed numbers lose their sign. */ + if (lval == 0 && ntype == fmt[SDEC]) + ntype = fmt[DEC]; + nlen = snprintf(nbuf, sizeof(nbuf), ntype, lval); + } else { + if ((nret = nget_uslong(&ulval, t, NULL, base)) != NUM_OK) + goto err; + if (vp->character == '+') { + if (!NPFITS(ULONG_MAX, ulval, change)) { + nret = NUM_OVER; + goto err; + } + ulval += change; + } else { + if (ulval < change) { + nret = NUM_UNDER; + goto err; + } + ulval -= change; + } + + /* Correct for literal "0[Xx]" in format. */ + if (base == 16) + wlen -= 2; + + nlen = snprintf(nbuf, sizeof(nbuf), ntype, wlen, ulval); + } + + /* Build the new line. */ + memmove(bp, p, beg); + memmove(bp + beg, nbuf, nlen); + memmove(bp + beg + nlen, p + end, len - beg - (end - beg)); + len = beg + nlen + (len - beg - (end - beg)); + + nret = NUM_OK; + rval = db_set(sp, vp->m_start.lno, bp, len); + + if (0) { +err: rval = 1; + inc_err(sp, nret); + } + if (bp != NULL) + FREE_SPACE(sp, bp, blen); + return (rval); +} + +static void +inc_err(sp, nret) + SCR *sp; + enum nresult nret; +{ + switch (nret) { + case NUM_ERR: + break; + case NUM_OK: + abort(); + /* NOREACHED */ + case NUM_OVER: + msgq(sp, M_ERR, "182|Resulting number too large"); + break; + case NUM_UNDER: + msgq(sp, M_ERR, "183|Resulting number too small"); + break; + } +} |