1208987a5SXin LI /* $NetBSD: seq.c,v 1.5 2008/07/21 14:19:26 lukem Exp $ */ 2208987a5SXin LI /* 3208987a5SXin LI * Copyright (c) 2005 The NetBSD Foundation, Inc. 4208987a5SXin LI * All rights reserved. 5208987a5SXin LI * 6208987a5SXin LI * This code is derived from software contributed to The NetBSD Foundation 7208987a5SXin LI * by Brian Ginsbach. 8208987a5SXin LI * 9208987a5SXin LI * Redistribution and use in source and binary forms, with or without 10208987a5SXin LI * modification, are permitted provided that the following conditions 11208987a5SXin LI * are met: 12208987a5SXin LI * 1. Redistributions of source code must retain the above copyright 13208987a5SXin LI * notice, this list of conditions and the following disclaimer. 14208987a5SXin LI * 2. Redistributions in binary form must reproduce the above copyright 15208987a5SXin LI * notice, this list of conditions and the following disclaimer in the 16208987a5SXin LI * documentation and/or other materials provided with the distribution. 17208987a5SXin LI * 18208987a5SXin LI * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 19208987a5SXin LI * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20208987a5SXin LI * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21208987a5SXin LI * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 22208987a5SXin LI * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23208987a5SXin LI * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24208987a5SXin LI * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25208987a5SXin LI * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26208987a5SXin LI * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27208987a5SXin LI * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28208987a5SXin LI * POSSIBILITY OF SUCH DAMAGE. 29208987a5SXin LI */ 30208987a5SXin LI 31208987a5SXin LI #include <sys/cdefs.h> 32208987a5SXin LI __FBSDID("$FreeBSD$"); 33208987a5SXin LI 34208987a5SXin LI #include <ctype.h> 35208987a5SXin LI #include <err.h> 36208987a5SXin LI #include <errno.h> 37208987a5SXin LI #include <math.h> 38208987a5SXin LI #include <locale.h> 39208987a5SXin LI #include <stdio.h> 40208987a5SXin LI #include <stdlib.h> 41208987a5SXin LI #include <string.h> 42208987a5SXin LI #include <unistd.h> 43208987a5SXin LI 44208987a5SXin LI #define ZERO '0' 45208987a5SXin LI #define SPACE ' ' 46208987a5SXin LI 47208987a5SXin LI #define MAX(a, b) (((a) < (b))? (b) : (a)) 48208987a5SXin LI #define ISSIGN(c) ((int)(c) == '-' || (int)(c) == '+') 49208987a5SXin LI #define ISEXP(c) ((int)(c) == 'e' || (int)(c) == 'E') 50208987a5SXin LI #define ISODIGIT(c) ((int)(c) >= '0' && (int)(c) <= '7') 51208987a5SXin LI 52208987a5SXin LI /* Globals */ 53208987a5SXin LI 54*cfbd8d46SEd Schouten static const char *decimal_point = "."; /* default */ 55*cfbd8d46SEd Schouten static char default_format[] = { "%g" }; /* default */ 56208987a5SXin LI 57208987a5SXin LI /* Prototypes */ 58208987a5SXin LI 59*cfbd8d46SEd Schouten static double e_atof(const char *); 60208987a5SXin LI 61*cfbd8d46SEd Schouten static int decimal_places(const char *); 62*cfbd8d46SEd Schouten static int numeric(const char *); 63*cfbd8d46SEd Schouten static int valid_format(const char *); 64208987a5SXin LI 65*cfbd8d46SEd Schouten static char *generate_format(double, double, double, int, char); 66*cfbd8d46SEd Schouten static char *unescape(char *); 67208987a5SXin LI 68208987a5SXin LI /* 69208987a5SXin LI * The seq command will print out a numeric sequence from 1, the default, 70208987a5SXin LI * to a user specified upper limit by 1. The lower bound and increment 71208987a5SXin LI * maybe indicated by the user on the command line. The sequence can 72208987a5SXin LI * be either whole, the default, or decimal numbers. 73208987a5SXin LI */ 74208987a5SXin LI int 75208987a5SXin LI main(int argc, char *argv[]) 76208987a5SXin LI { 77208987a5SXin LI int c = 0, errflg = 0; 78208987a5SXin LI int equalize = 0; 79208987a5SXin LI double first = 1.0; 80208987a5SXin LI double last = 0.0; 81208987a5SXin LI double incr = 0.0; 82208987a5SXin LI struct lconv *locale; 83208987a5SXin LI char *fmt = NULL; 84208987a5SXin LI const char *sep = "\n"; 85208987a5SXin LI const char *term = NULL; 86208987a5SXin LI char pad = ZERO; 87208987a5SXin LI 88208987a5SXin LI /* Determine the locale's decimal point. */ 89208987a5SXin LI locale = localeconv(); 90208987a5SXin LI if (locale && locale->decimal_point && locale->decimal_point[0] != '\0') 91208987a5SXin LI decimal_point = locale->decimal_point; 92208987a5SXin LI 93208987a5SXin LI /* 94208987a5SXin LI * Process options, but handle negative numbers separately 95208987a5SXin LI * least they trip up getopt(3). 96208987a5SXin LI */ 97208987a5SXin LI while ((optind < argc) && !numeric(argv[optind]) && 98208987a5SXin LI (c = getopt(argc, argv, "f:hs:t:w")) != -1) { 99208987a5SXin LI 100208987a5SXin LI switch (c) { 101208987a5SXin LI case 'f': /* format (plan9) */ 102208987a5SXin LI fmt = optarg; 103208987a5SXin LI equalize = 0; 104208987a5SXin LI break; 105208987a5SXin LI case 's': /* separator (GNU) */ 106208987a5SXin LI sep = unescape(optarg); 107208987a5SXin LI break; 108208987a5SXin LI case 't': /* terminator (new) */ 109208987a5SXin LI term = unescape(optarg); 110208987a5SXin LI break; 111208987a5SXin LI case 'w': /* equal width (plan9) */ 112208987a5SXin LI if (!fmt) 113208987a5SXin LI if (equalize++) 114208987a5SXin LI pad = SPACE; 115208987a5SXin LI break; 116208987a5SXin LI case 'h': /* help (GNU) */ 117208987a5SXin LI default: 118208987a5SXin LI errflg++; 119208987a5SXin LI break; 120208987a5SXin LI } 121208987a5SXin LI } 122208987a5SXin LI 123208987a5SXin LI argc -= optind; 124208987a5SXin LI argv += optind; 125208987a5SXin LI if (argc < 1 || argc > 3) 126208987a5SXin LI errflg++; 127208987a5SXin LI 128208987a5SXin LI if (errflg) { 129208987a5SXin LI fprintf(stderr, 130208987a5SXin LI "usage: %s [-w] [-f format] [-s string] [-t string] [first [incr]] last\n", 131208987a5SXin LI getprogname()); 132208987a5SXin LI exit(1); 133208987a5SXin LI } 134208987a5SXin LI 135208987a5SXin LI last = e_atof(argv[argc - 1]); 136208987a5SXin LI 137208987a5SXin LI if (argc > 1) 138208987a5SXin LI first = e_atof(argv[0]); 139208987a5SXin LI 140208987a5SXin LI if (argc > 2) { 141208987a5SXin LI incr = e_atof(argv[1]); 142208987a5SXin LI /* Plan 9/GNU don't do zero */ 143208987a5SXin LI if (incr == 0.0) 144208987a5SXin LI errx(1, "zero %screment", (first < last)? "in" : "de"); 145208987a5SXin LI } 146208987a5SXin LI 147208987a5SXin LI /* default is one for Plan 9/GNU work alike */ 148208987a5SXin LI if (incr == 0.0) 149208987a5SXin LI incr = (first < last) ? 1.0 : -1.0; 150208987a5SXin LI 151208987a5SXin LI if (incr <= 0.0 && first < last) 152208987a5SXin LI errx(1, "needs positive increment"); 153208987a5SXin LI 154208987a5SXin LI if (incr >= 0.0 && first > last) 155208987a5SXin LI errx(1, "needs negative decrement"); 156208987a5SXin LI 157208987a5SXin LI if (fmt != NULL) { 158208987a5SXin LI if (!valid_format(fmt)) 159208987a5SXin LI errx(1, "invalid format string: `%s'", fmt); 160208987a5SXin LI fmt = unescape(fmt); 161208987a5SXin LI /* 162208987a5SXin LI * XXX to be bug for bug compatible with Plan 9 add a 163208987a5SXin LI * newline if none found at the end of the format string. 164208987a5SXin LI */ 165208987a5SXin LI } else 166208987a5SXin LI fmt = generate_format(first, incr, last, equalize, pad); 167208987a5SXin LI 168208987a5SXin LI if (incr > 0) { 169208987a5SXin LI for (; first <= last; first += incr) { 170208987a5SXin LI printf(fmt, first); 171208987a5SXin LI fputs(sep, stdout); 172208987a5SXin LI } 173208987a5SXin LI } else { 174208987a5SXin LI for (; first >= last; first += incr) { 175208987a5SXin LI printf(fmt, first); 176208987a5SXin LI fputs(sep, stdout); 177208987a5SXin LI } 178208987a5SXin LI } 179208987a5SXin LI if (term != NULL) 180208987a5SXin LI fputs(term, stdout); 181208987a5SXin LI 182208987a5SXin LI return (0); 183208987a5SXin LI } 184208987a5SXin LI 185208987a5SXin LI /* 186208987a5SXin LI * numeric - verify that string is numeric 187208987a5SXin LI */ 188*cfbd8d46SEd Schouten static int 189208987a5SXin LI numeric(const char *s) 190208987a5SXin LI { 191208987a5SXin LI int seen_decimal_pt, decimal_pt_len; 192208987a5SXin LI 193208987a5SXin LI /* skip any sign */ 194208987a5SXin LI if (ISSIGN((unsigned char)*s)) 195208987a5SXin LI s++; 196208987a5SXin LI 197208987a5SXin LI seen_decimal_pt = 0; 198208987a5SXin LI decimal_pt_len = strlen(decimal_point); 199208987a5SXin LI while (*s) { 200208987a5SXin LI if (!isdigit((unsigned char)*s)) { 201208987a5SXin LI if (!seen_decimal_pt && 202208987a5SXin LI strncmp(s, decimal_point, decimal_pt_len) == 0) { 203208987a5SXin LI s += decimal_pt_len; 204208987a5SXin LI seen_decimal_pt = 1; 205208987a5SXin LI continue; 206208987a5SXin LI } 207208987a5SXin LI if (ISEXP((unsigned char)*s)) { 208208987a5SXin LI s++; 2090a167a9bSXin LI if (ISSIGN((unsigned char)*s) || 2100a167a9bSXin LI isdigit((unsigned char)*s)) { 211208987a5SXin LI s++; 212208987a5SXin LI continue; 213208987a5SXin LI } 214208987a5SXin LI } 215208987a5SXin LI break; 216208987a5SXin LI } 217208987a5SXin LI s++; 218208987a5SXin LI } 219208987a5SXin LI return (*s == '\0'); 220208987a5SXin LI } 221208987a5SXin LI 222208987a5SXin LI /* 223208987a5SXin LI * valid_format - validate user specified format string 224208987a5SXin LI */ 225*cfbd8d46SEd Schouten static int 226208987a5SXin LI valid_format(const char *fmt) 227208987a5SXin LI { 228208987a5SXin LI int conversions = 0; 229208987a5SXin LI 230208987a5SXin LI while (*fmt != '\0') { 231208987a5SXin LI /* scan for conversions */ 232208987a5SXin LI if (*fmt != '\0' && *fmt != '%') { 233208987a5SXin LI do { 234208987a5SXin LI fmt++; 235208987a5SXin LI } while (*fmt != '\0' && *fmt != '%'); 236208987a5SXin LI } 237208987a5SXin LI /* scan a conversion */ 238208987a5SXin LI if (*fmt != '\0') { 239208987a5SXin LI do { 240208987a5SXin LI fmt++; 241208987a5SXin LI 242208987a5SXin LI /* ok %% */ 243208987a5SXin LI if (*fmt == '%') { 244208987a5SXin LI fmt++; 245208987a5SXin LI break; 246208987a5SXin LI } 247208987a5SXin LI /* valid conversions */ 248208987a5SXin LI if (strchr("eEfgG", *fmt) && 249208987a5SXin LI conversions++ < 1) { 250208987a5SXin LI fmt++; 251208987a5SXin LI break; 252208987a5SXin LI } 253b1ce21c6SRebecca Cran /* flags, width and precision */ 254208987a5SXin LI if (isdigit((unsigned char)*fmt) || 255208987a5SXin LI strchr("+- 0#.", *fmt)) 256208987a5SXin LI continue; 257208987a5SXin LI 258208987a5SXin LI /* oops! bad conversion format! */ 259208987a5SXin LI return (0); 260208987a5SXin LI } while (*fmt != '\0'); 261208987a5SXin LI } 262208987a5SXin LI } 263208987a5SXin LI 264208987a5SXin LI return (conversions <= 1); 265208987a5SXin LI } 266208987a5SXin LI 267208987a5SXin LI /* 268208987a5SXin LI * unescape - handle C escapes in a string 269208987a5SXin LI */ 270*cfbd8d46SEd Schouten static char * 271208987a5SXin LI unescape(char *orig) 272208987a5SXin LI { 273208987a5SXin LI char c, *cp, *new = orig; 274208987a5SXin LI int i; 275208987a5SXin LI 276208987a5SXin LI for (cp = orig; (*orig = *cp); cp++, orig++) { 277208987a5SXin LI if (*cp != '\\') 278208987a5SXin LI continue; 279208987a5SXin LI 280208987a5SXin LI switch (*++cp) { 281208987a5SXin LI case 'a': /* alert (bell) */ 282208987a5SXin LI *orig = '\a'; 283208987a5SXin LI continue; 284208987a5SXin LI case 'b': /* backspace */ 285208987a5SXin LI *orig = '\b'; 286208987a5SXin LI continue; 287208987a5SXin LI case 'e': /* escape */ 288208987a5SXin LI *orig = '\e'; 289208987a5SXin LI continue; 290208987a5SXin LI case 'f': /* formfeed */ 291208987a5SXin LI *orig = '\f'; 292208987a5SXin LI continue; 293208987a5SXin LI case 'n': /* newline */ 294208987a5SXin LI *orig = '\n'; 295208987a5SXin LI continue; 296208987a5SXin LI case 'r': /* carriage return */ 297208987a5SXin LI *orig = '\r'; 298208987a5SXin LI continue; 299208987a5SXin LI case 't': /* horizontal tab */ 300208987a5SXin LI *orig = '\t'; 301208987a5SXin LI continue; 302208987a5SXin LI case 'v': /* vertical tab */ 303208987a5SXin LI *orig = '\v'; 304208987a5SXin LI continue; 305208987a5SXin LI case '\\': /* backslash */ 306208987a5SXin LI *orig = '\\'; 307208987a5SXin LI continue; 308208987a5SXin LI case '\'': /* single quote */ 309208987a5SXin LI *orig = '\''; 310208987a5SXin LI continue; 311208987a5SXin LI case '\"': /* double quote */ 312208987a5SXin LI *orig = '"'; 313208987a5SXin LI continue; 314208987a5SXin LI case '0': 315208987a5SXin LI case '1': 316208987a5SXin LI case '2': 317208987a5SXin LI case '3': /* octal */ 318208987a5SXin LI case '4': 319208987a5SXin LI case '5': 320208987a5SXin LI case '6': 321208987a5SXin LI case '7': /* number */ 322208987a5SXin LI for (i = 0, c = 0; 323208987a5SXin LI ISODIGIT((unsigned char)*cp) && i < 3; 324208987a5SXin LI i++, cp++) { 325208987a5SXin LI c <<= 3; 326208987a5SXin LI c |= (*cp - '0'); 327208987a5SXin LI } 328208987a5SXin LI *orig = c; 329208987a5SXin LI --cp; 330208987a5SXin LI continue; 331b1ce21c6SRebecca Cran case 'x': /* hexadecimal number */ 332208987a5SXin LI cp++; /* skip 'x' */ 333208987a5SXin LI for (i = 0, c = 0; 334208987a5SXin LI isxdigit((unsigned char)*cp) && i < 2; 335208987a5SXin LI i++, cp++) { 336208987a5SXin LI c <<= 4; 337208987a5SXin LI if (isdigit((unsigned char)*cp)) 338208987a5SXin LI c |= (*cp - '0'); 339208987a5SXin LI else 340208987a5SXin LI c |= ((toupper((unsigned char)*cp) - 341208987a5SXin LI 'A') + 10); 342208987a5SXin LI } 343208987a5SXin LI *orig = c; 344208987a5SXin LI --cp; 345208987a5SXin LI continue; 346208987a5SXin LI default: 347208987a5SXin LI --cp; 348208987a5SXin LI break; 349208987a5SXin LI } 350208987a5SXin LI } 351208987a5SXin LI 352208987a5SXin LI return (new); 353208987a5SXin LI } 354208987a5SXin LI 355208987a5SXin LI /* 356208987a5SXin LI * e_atof - convert an ASCII string to a double 357208987a5SXin LI * exit if string is not a valid double, or if converted value would 358208987a5SXin LI * cause overflow or underflow 359208987a5SXin LI */ 360*cfbd8d46SEd Schouten static double 361208987a5SXin LI e_atof(const char *num) 362208987a5SXin LI { 363208987a5SXin LI char *endp; 364208987a5SXin LI double dbl; 365208987a5SXin LI 366208987a5SXin LI errno = 0; 367208987a5SXin LI dbl = strtod(num, &endp); 368208987a5SXin LI 369208987a5SXin LI if (errno == ERANGE) 370208987a5SXin LI /* under or overflow */ 371208987a5SXin LI err(2, "%s", num); 372208987a5SXin LI else if (*endp != '\0') 373208987a5SXin LI /* "junk" left in number */ 374208987a5SXin LI errx(2, "invalid floating point argument: %s", num); 375208987a5SXin LI 376208987a5SXin LI /* zero shall have no sign */ 377208987a5SXin LI if (dbl == -0.0) 378208987a5SXin LI dbl = 0; 379208987a5SXin LI return (dbl); 380208987a5SXin LI } 381208987a5SXin LI 382208987a5SXin LI /* 383208987a5SXin LI * decimal_places - count decimal places in a number (string) 384208987a5SXin LI */ 385*cfbd8d46SEd Schouten static int 386208987a5SXin LI decimal_places(const char *number) 387208987a5SXin LI { 388208987a5SXin LI int places = 0; 389208987a5SXin LI char *dp; 390208987a5SXin LI 391208987a5SXin LI /* look for a decimal point */ 392208987a5SXin LI if ((dp = strstr(number, decimal_point))) { 393208987a5SXin LI dp += strlen(decimal_point); 394208987a5SXin LI 395208987a5SXin LI while (isdigit((unsigned char)*dp++)) 396208987a5SXin LI places++; 397208987a5SXin LI } 398208987a5SXin LI return (places); 399208987a5SXin LI } 400208987a5SXin LI 401208987a5SXin LI /* 402208987a5SXin LI * generate_format - create a format string 403208987a5SXin LI * 404b1ce21c6SRebecca Cran * XXX to be bug for bug compatible with Plan9 and GNU return "%g" 405208987a5SXin LI * when "%g" prints as "%e" (this way no width adjustments are made) 406208987a5SXin LI */ 407*cfbd8d46SEd Schouten static char * 408208987a5SXin LI generate_format(double first, double incr, double last, int equalize, char pad) 409208987a5SXin LI { 410208987a5SXin LI static char buf[256]; 411208987a5SXin LI char cc = '\0'; 412208987a5SXin LI int precision, width1, width2, places; 413208987a5SXin LI 414208987a5SXin LI if (equalize == 0) 415208987a5SXin LI return (default_format); 416208987a5SXin LI 417208987a5SXin LI /* figure out "last" value printed */ 418208987a5SXin LI if (first > last) 419208987a5SXin LI last = first - incr * floor((first - last) / incr); 420208987a5SXin LI else 421208987a5SXin LI last = first + incr * floor((last - first) / incr); 422208987a5SXin LI 423208987a5SXin LI sprintf(buf, "%g", incr); 424208987a5SXin LI if (strchr(buf, 'e')) 425208987a5SXin LI cc = 'e'; 426208987a5SXin LI precision = decimal_places(buf); 427208987a5SXin LI 428208987a5SXin LI width1 = sprintf(buf, "%g", first); 429208987a5SXin LI if (strchr(buf, 'e')) 430208987a5SXin LI cc = 'e'; 431208987a5SXin LI if ((places = decimal_places(buf))) 432208987a5SXin LI width1 -= (places + strlen(decimal_point)); 433208987a5SXin LI 434208987a5SXin LI precision = MAX(places, precision); 435208987a5SXin LI 436208987a5SXin LI width2 = sprintf(buf, "%g", last); 437208987a5SXin LI if (strchr(buf, 'e')) 438208987a5SXin LI cc = 'e'; 439208987a5SXin LI if ((places = decimal_places(buf))) 440208987a5SXin LI width2 -= (places + strlen(decimal_point)); 441208987a5SXin LI 442208987a5SXin LI if (precision) { 443208987a5SXin LI sprintf(buf, "%%%c%d.%d%c", pad, 444208987a5SXin LI MAX(width1, width2) + (int) strlen(decimal_point) + 445208987a5SXin LI precision, precision, (cc) ? cc : 'f'); 446208987a5SXin LI } else { 447208987a5SXin LI sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2), 448208987a5SXin LI (cc) ? cc : 'g'); 449208987a5SXin LI } 450208987a5SXin LI 451208987a5SXin LI return (buf); 452208987a5SXin LI } 453