[BACK]Return to seq.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / seq

Annotation of src/usr.bin/seq/seq.c, Revision 1.1

1.1     ! millert     1: /*     $OpenBSD$       */
        !             2:
        !             3: /*-
        !             4:  * Copyright (c) 2005 The NetBSD Foundation, Inc.
        !             5:  * All rights reserved.
        !             6:  *
        !             7:  * This code is derived from software contributed to The NetBSD Foundation
        !             8:  * by Brian Ginsbach.
        !             9:  *
        !            10:  * Redistribution and use in source and binary forms, with or without
        !            11:  * modification, are permitted provided that the following conditions
        !            12:  * are met:
        !            13:  * 1. Redistributions of source code must retain the above copyright
        !            14:  *    notice, this list of conditions and the following disclaimer.
        !            15:  * 2. Redistributions in binary form must reproduce the above copyright
        !            16:  *    notice, this list of conditions and the following disclaimer in the
        !            17:  *    documentation and/or other materials provided with the distribution.
        !            18:  *
        !            19:  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
        !            20:  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
        !            21:  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
        !            22:  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
        !            23:  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
        !            24:  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
        !            25:  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
        !            26:  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
        !            27:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
        !            28:  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
        !            29:  * POSSIBILITY OF SUCH DAMAGE.
        !            30:  */
        !            31:
        !            32: #include <ctype.h>
        !            33: #include <err.h>
        !            34: #include <errno.h>
        !            35: #include <getopt.h>
        !            36: #include <math.h>
        !            37: #include <locale.h>
        !            38: #include <stdio.h>
        !            39: #include <stdlib.h>
        !            40: #include <string.h>
        !            41: #include <unistd.h>
        !            42:
        !            43: #define VERSION        "1.0"
        !            44: #define ZERO   '0'
        !            45: #define SPACE  ' '
        !            46:
        !            47: #define MAXIMUM(a, b)  (((a) < (b))? (b) : (a))
        !            48: #define ISSIGN(c)      ((int)(c) == '-' || (int)(c) == '+')
        !            49: #define ISEXP(c)       ((int)(c) == 'e' || (int)(c) == 'E')
        !            50: #define ISODIGIT(c)    ((int)(c) >= '0' && (int)(c) <= '7')
        !            51:
        !            52: /* Globals */
        !            53:
        !            54: static const char *decimal_point = ".";        /* default */
        !            55: static char default_format[] = { "%g" };       /* default */
        !            56:
        !            57: static const struct option long_opts[] =
        !            58: {
        !            59:        {"format",      required_argument,      NULL, 'f'},
        !            60:        {"help",        no_argument,            NULL, 'h'},
        !            61:        {"separator",   required_argument,      NULL, 's'},
        !            62:        {"version",     no_argument,            NULL, 'v'},
        !            63:        {"equal-width", no_argument,            NULL, 'w'},
        !            64:        {NULL,          no_argument,            NULL, 0}
        !            65: };
        !            66:
        !            67: /* Prototypes */
        !            68:
        !            69: static double e_atof(const char *);
        !            70:
        !            71: static int decimal_places(const char *);
        !            72: static int numeric(const char *);
        !            73: static int valid_format(const char *);
        !            74:
        !            75: static char *generate_format(double, double, double, int, char);
        !            76:
        !            77: static __dead void usage(int error);
        !            78:
        !            79: /*
        !            80:  * The seq command will print out a numeric sequence from 1, the default,
        !            81:  * to a user specified upper limit by 1.  The lower bound and increment
        !            82:  * maybe indicated by the user on the command line.  The sequence can
        !            83:  * be either whole, the default, or decimal numbers.
        !            84:  */
        !            85: int
        !            86: main(int argc, char *argv[])
        !            87: {
        !            88:        int c = 0;
        !            89:        int equalize = 0;
        !            90:        double first = 1.0;
        !            91:        double last = 0.0;
        !            92:        double incr = 0.0;
        !            93:        double last_shown_value = 0.0;
        !            94:        double cur, step;
        !            95:        struct lconv *locale;
        !            96:        char *fmt = NULL;
        !            97:        const char *sep = "\n";
        !            98:        const char *term = "\n";
        !            99:        char *cur_print, *last_print;
        !           100:        char pad = ZERO;
        !           101:
        !           102:        /* Determine the locale's decimal point. */
        !           103:        locale = localeconv();
        !           104:        if (locale && locale->decimal_point && locale->decimal_point[0] != '\0')
        !           105:                decimal_point = locale->decimal_point;
        !           106:
        !           107:        /*
        !           108:          * Process options, but handle negative numbers separately
        !           109:          * least they trip up getopt(3).
        !           110:          */
        !           111:        while ((optind < argc) && !numeric(argv[optind]) &&
        !           112:            (c = getopt_long(argc, argv, "+f:s:w", long_opts, NULL)) != -1) {
        !           113:
        !           114:                switch (c) {
        !           115:                case 'f':       /* format (plan9/GNU) */
        !           116:                        fmt = optarg;
        !           117:                        equalize = 0;
        !           118:                        break;
        !           119:                case 's':       /* separator (GNU) */
        !           120:                        sep = optarg;
        !           121:                        break;
        !           122:                case 'v':       /* version (GNU) */
        !           123:                        printf("seq version %s\n", VERSION);
        !           124:                        return 0;
        !           125:                case 'w':       /* equal width (plan9/GNU) */
        !           126:                        if (fmt == NULL) {
        !           127:                                if (equalize++)
        !           128:                                        pad = SPACE;
        !           129:                        }
        !           130:                        break;
        !           131:                case 'h':       /* help (GNU) */
        !           132:                        usage(0);
        !           133:                        break;
        !           134:                default:
        !           135:                        usage(1);
        !           136:                        break;
        !           137:                }
        !           138:        }
        !           139:
        !           140:        argc -= optind;
        !           141:        argv += optind;
        !           142:        if (argc < 1 || argc > 3)
        !           143:                usage(1);
        !           144:
        !           145:        last = e_atof(argv[argc - 1]);
        !           146:
        !           147:        if (argc > 1)
        !           148:                first = e_atof(argv[0]);
        !           149:
        !           150:        if (argc > 2) {
        !           151:                incr = e_atof(argv[1]);
        !           152:                /* Plan 9/GNU don't do zero */
        !           153:                if (incr == 0.0)
        !           154:                        errx(1, "zero %screment", (first < last)? "in" : "de");
        !           155:        }
        !           156:
        !           157:        /* default is one for Plan 9/GNU work alike */
        !           158:        if (incr == 0.0)
        !           159:                incr = (first < last) ? 1.0 : -1.0;
        !           160:
        !           161:        if (incr <= 0.0 && first < last)
        !           162:                errx(1, "needs positive increment");
        !           163:
        !           164:        if (incr >= 0.0 && first > last)
        !           165:                errx(1, "needs negative decrement");
        !           166:
        !           167:        if (fmt != NULL) {
        !           168:                if (!valid_format(fmt))
        !           169:                        errx(1, "invalid format string: `%s'", fmt);
        !           170:                /*
        !           171:                 * XXX to be bug for bug compatible with Plan 9 add a
        !           172:                 * newline if none found at the end of the format string.
        !           173:                 */
        !           174:        } else
        !           175:                fmt = generate_format(first, incr, last, equalize, pad);
        !           176:
        !           177:        for (step = 1, cur = first; incr > 0 ? cur <= last : cur >= last;
        !           178:            cur = first + incr * step++) {
        !           179:                if (cur != first)
        !           180:                    fputs(sep, stdout);
        !           181:                printf(fmt, cur);
        !           182:                last_shown_value = cur;
        !           183:        }
        !           184:
        !           185:        /*
        !           186:         * Did we miss the last value of the range in the loop above?
        !           187:         *
        !           188:         * We might have, so check if the printable version of the last
        !           189:         * computed value ('cur') and desired 'last' value are equal.  If they
        !           190:         * are equal after formatting truncation, but 'cur' and
        !           191:         * 'last_shown_value' are not equal, it means the exit condition of the
        !           192:         * loop held true due to a rounding error and we still need to print
        !           193:         * 'last'.
        !           194:         */
        !           195:        asprintf(&cur_print, fmt, cur);
        !           196:        asprintf(&last_print, fmt, last);
        !           197:        if (strcmp(cur_print, last_print) == 0 && cur != last_shown_value) {
        !           198:                if (cur != first)
        !           199:                    fputs(sep, stdout);
        !           200:                fputs(last_print, stdout);
        !           201:        }
        !           202:        free(cur_print);
        !           203:        free(last_print);
        !           204:
        !           205:        fputs(term, stdout);
        !           206:
        !           207:        return 0;
        !           208: }
        !           209:
        !           210: /*
        !           211:  * numeric - verify that string is numeric
        !           212:  */
        !           213: static int
        !           214: numeric(const char *s)
        !           215: {
        !           216:        int seen_decimal_pt, decimal_pt_len;
        !           217:
        !           218:        /* skip any sign */
        !           219:        if (ISSIGN((unsigned char)*s))
        !           220:                s++;
        !           221:
        !           222:        seen_decimal_pt = 0;
        !           223:        decimal_pt_len = strlen(decimal_point);
        !           224:        while (*s) {
        !           225:                if (!isdigit((unsigned char)*s)) {
        !           226:                        if (!seen_decimal_pt &&
        !           227:                            strncmp(s, decimal_point, decimal_pt_len) == 0) {
        !           228:                                s += decimal_pt_len;
        !           229:                                seen_decimal_pt = 1;
        !           230:                                continue;
        !           231:                        }
        !           232:                        if (ISEXP((unsigned char)*s)) {
        !           233:                                s++;
        !           234:                                if (ISSIGN((unsigned char)*s) ||
        !           235:                                    isdigit((unsigned char)*s)) {
        !           236:                                        s++;
        !           237:                                        continue;
        !           238:                                }
        !           239:                        }
        !           240:                        break;
        !           241:                }
        !           242:                s++;
        !           243:        }
        !           244:        return *s == '\0';
        !           245: }
        !           246:
        !           247: /*
        !           248:  * valid_format - validate user specified format string
        !           249:  */
        !           250: static int
        !           251: valid_format(const char *fmt)
        !           252: {
        !           253:        unsigned conversions = 0;
        !           254:
        !           255:        while (*fmt != '\0') {
        !           256:                /* scan for conversions */
        !           257:                if (*fmt != '%') {
        !           258:                        fmt++;
        !           259:                        continue;
        !           260:                }
        !           261:                fmt++;
        !           262:
        !           263:                /* allow %% but not things like %10% */
        !           264:                if (*fmt == '%') {
        !           265:                        fmt++;
        !           266:                        continue;
        !           267:                }
        !           268:
        !           269:                /* flags */
        !           270:                while (*fmt != '\0' && strchr("#0- +'", *fmt)) {
        !           271:                        fmt++;
        !           272:                }
        !           273:
        !           274:                /* field width */
        !           275:                while (*fmt != '\0' && strchr("0123456789", *fmt)) {
        !           276:                        fmt++;
        !           277:                }
        !           278:
        !           279:                /* precision */
        !           280:                if (*fmt == '.') {
        !           281:                        fmt++;
        !           282:                        while (*fmt != '\0' && strchr("0123456789", *fmt)) {
        !           283:                                fmt++;
        !           284:                        }
        !           285:                }
        !           286:
        !           287:                /* conversion */
        !           288:                switch (*fmt) {
        !           289:                    case 'A':
        !           290:                    case 'a':
        !           291:                    case 'E':
        !           292:                    case 'e':
        !           293:                    case 'F':
        !           294:                    case 'f':
        !           295:                    case 'G':
        !           296:                    case 'g':
        !           297:                        /* floating point formats are accepted */
        !           298:                        conversions++;
        !           299:                        break;
        !           300:                    default:
        !           301:                        /* anything else is not */
        !           302:                        return 0;
        !           303:                }
        !           304:        }
        !           305:
        !           306:        /* PR 236347 -- user format strings must have a conversion */
        !           307:        return conversions == 1;
        !           308: }
        !           309:
        !           310: /*
        !           311:  * e_atof - convert an ASCII string to a double
        !           312:  *     exit if string is not a valid double, or if converted value would
        !           313:  *     cause overflow or underflow
        !           314:  */
        !           315: static double
        !           316: e_atof(const char *num)
        !           317: {
        !           318:        char *endp;
        !           319:        double dbl;
        !           320:
        !           321:        errno = 0;
        !           322:        dbl = strtod(num, &endp);
        !           323:
        !           324:        if (errno == ERANGE)
        !           325:                /* under or overflow */
        !           326:                err(2, "%s", num);
        !           327:        else if (*endp != '\0')
        !           328:                /* "junk" left in number */
        !           329:                errx(2, "invalid floating point argument: %s", num);
        !           330:
        !           331:        /* zero shall have no sign */
        !           332:        if (dbl == -0.0)
        !           333:                dbl = 0;
        !           334:        return dbl;
        !           335: }
        !           336:
        !           337: /*
        !           338:  * decimal_places - count decimal places in a number (string)
        !           339:  */
        !           340: static int
        !           341: decimal_places(const char *number)
        !           342: {
        !           343:        int places = 0;
        !           344:        char *dp;
        !           345:
        !           346:        /* look for a decimal point */
        !           347:        if ((dp = strstr(number, decimal_point))) {
        !           348:                dp += strlen(decimal_point);
        !           349:
        !           350:                while (isdigit((unsigned char)*dp++))
        !           351:                        places++;
        !           352:        }
        !           353:        return places;
        !           354: }
        !           355:
        !           356: /*
        !           357:  * generate_format - create a format string
        !           358:  *
        !           359:  * XXX to be bug for bug compatible with Plan9 and GNU return "%g"
        !           360:  * when "%g" prints as "%e" (this way no width adjustments are made)
        !           361:  */
        !           362: static char *
        !           363: generate_format(double first, double incr, double last, int equalize, char pad)
        !           364: {
        !           365:        static char buf[256];
        !           366:        char cc = '\0';
        !           367:        int precision, width1, width2, places;
        !           368:
        !           369:        if (equalize == 0)
        !           370:                return default_format;
        !           371:
        !           372:        /* figure out "last" value printed */
        !           373:        if (first > last)
        !           374:                last = first - incr * floor((first - last) / incr);
        !           375:        else
        !           376:                last = first + incr * floor((last - first) / incr);
        !           377:
        !           378:        snprintf(buf, sizeof(buf), "%g", incr);
        !           379:        if (strchr(buf, 'e'))
        !           380:                cc = 'e';
        !           381:        precision = decimal_places(buf);
        !           382:
        !           383:        width1 = snprintf(buf, sizeof(buf), "%g", first);
        !           384:        if (strchr(buf, 'e'))
        !           385:                cc = 'e';
        !           386:        if ((places = decimal_places(buf)))
        !           387:                width1 -= (places + strlen(decimal_point));
        !           388:
        !           389:        precision = MAXIMUM(places, precision);
        !           390:
        !           391:        width2 = snprintf(buf, sizeof(buf), "%g", last);
        !           392:        if (strchr(buf, 'e'))
        !           393:                cc = 'e';
        !           394:        if ((places = decimal_places(buf)))
        !           395:                width2 -= (places + strlen(decimal_point));
        !           396:
        !           397:        /* XXX if incr is floating point fix the precision */
        !           398:        if (precision) {
        !           399:                snprintf(buf, sizeof(buf), "%%%c%d.%d%c", pad,
        !           400:                    MAXIMUM(width1, width2) + (int) strlen(decimal_point) +
        !           401:                    precision, precision, (cc) ? cc : 'f');
        !           402:        } else {
        !           403:                snprintf(buf, sizeof(buf), "%%%c%d%c", pad,
        !           404:                    MAXIMUM(width1, width2), (cc) ? cc : 'g');
        !           405:        }
        !           406:
        !           407:        return buf;
        !           408: }
        !           409:
        !           410: static __dead void
        !           411: usage(int error)
        !           412: {
        !           413:        fprintf(stderr,
        !           414:            "usage: %s [-vw] [-f format] [-s string] [first [incr]] last\n",
        !           415:            getprogname());
        !           416:        exit(error);
        !           417: }