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 54208987a5SXin LI const char *decimal_point = "."; /* default */ 55208987a5SXin LI char default_format[] = { "%g" }; /* default */ 56208987a5SXin LI 57208987a5SXin LI /* Prototypes */ 58208987a5SXin LI 59208987a5SXin LI double e_atof(const char *); 60208987a5SXin LI 61208987a5SXin LI int decimal_places(const char *); 62208987a5SXin LI int main(int, char *[]); 63208987a5SXin LI int numeric(const char *); 64208987a5SXin LI int valid_format(const char *); 65208987a5SXin LI 66208987a5SXin LI char *generate_format(double, double, double, int, char); 67208987a5SXin LI char *unescape(char *); 68208987a5SXin LI 69208987a5SXin LI /* 70208987a5SXin LI * The seq command will print out a numeric sequence from 1, the default, 71208987a5SXin LI * to a user specified upper limit by 1. The lower bound and increment 72208987a5SXin LI * maybe indicated by the user on the command line. The sequence can 73208987a5SXin LI * be either whole, the default, or decimal numbers. 74208987a5SXin LI */ 75208987a5SXin LI int 76208987a5SXin LI main(int argc, char *argv[]) 77208987a5SXin LI { 78208987a5SXin LI int c = 0, errflg = 0; 79208987a5SXin LI int equalize = 0; 80208987a5SXin LI double first = 1.0; 81208987a5SXin LI double last = 0.0; 82208987a5SXin LI double incr = 0.0; 83208987a5SXin LI struct lconv *locale; 84208987a5SXin LI char *fmt = NULL; 85208987a5SXin LI const char *sep = "\n"; 86208987a5SXin LI const char *term = NULL; 87208987a5SXin LI char pad = ZERO; 88208987a5SXin LI 89208987a5SXin LI /* Determine the locale's decimal point. */ 90208987a5SXin LI locale = localeconv(); 91208987a5SXin LI if (locale && locale->decimal_point && locale->decimal_point[0] != '\0') 92208987a5SXin LI decimal_point = locale->decimal_point; 93208987a5SXin LI 94208987a5SXin LI /* 95208987a5SXin LI * Process options, but handle negative numbers separately 96208987a5SXin LI * least they trip up getopt(3). 97208987a5SXin LI */ 98208987a5SXin LI while ((optind < argc) && !numeric(argv[optind]) && 99208987a5SXin LI (c = getopt(argc, argv, "f:hs:t:w")) != -1) { 100208987a5SXin LI 101208987a5SXin LI switch (c) { 102208987a5SXin LI case 'f': /* format (plan9) */ 103208987a5SXin LI fmt = optarg; 104208987a5SXin LI equalize = 0; 105208987a5SXin LI break; 106208987a5SXin LI case 's': /* separator (GNU) */ 107208987a5SXin LI sep = unescape(optarg); 108208987a5SXin LI break; 109208987a5SXin LI case 't': /* terminator (new) */ 110208987a5SXin LI term = unescape(optarg); 111208987a5SXin LI break; 112208987a5SXin LI case 'w': /* equal width (plan9) */ 113208987a5SXin LI if (!fmt) 114208987a5SXin LI if (equalize++) 115208987a5SXin LI pad = SPACE; 116208987a5SXin LI break; 117208987a5SXin LI case 'h': /* help (GNU) */ 118208987a5SXin LI default: 119208987a5SXin LI errflg++; 120208987a5SXin LI break; 121208987a5SXin LI } 122208987a5SXin LI } 123208987a5SXin LI 124208987a5SXin LI argc -= optind; 125208987a5SXin LI argv += optind; 126208987a5SXin LI if (argc < 1 || argc > 3) 127208987a5SXin LI errflg++; 128208987a5SXin LI 129208987a5SXin LI if (errflg) { 130208987a5SXin LI fprintf(stderr, 131208987a5SXin LI "usage: %s [-w] [-f format] [-s string] [-t string] [first [incr]] last\n", 132208987a5SXin LI getprogname()); 133208987a5SXin LI exit(1); 134208987a5SXin LI } 135208987a5SXin LI 136208987a5SXin LI last = e_atof(argv[argc - 1]); 137208987a5SXin LI 138208987a5SXin LI if (argc > 1) 139208987a5SXin LI first = e_atof(argv[0]); 140208987a5SXin LI 141208987a5SXin LI if (argc > 2) { 142208987a5SXin LI incr = e_atof(argv[1]); 143208987a5SXin LI /* Plan 9/GNU don't do zero */ 144208987a5SXin LI if (incr == 0.0) 145208987a5SXin LI errx(1, "zero %screment", (first < last)? "in" : "de"); 146208987a5SXin LI } 147208987a5SXin LI 148208987a5SXin LI /* default is one for Plan 9/GNU work alike */ 149208987a5SXin LI if (incr == 0.0) 150208987a5SXin LI incr = (first < last) ? 1.0 : -1.0; 151208987a5SXin LI 152208987a5SXin LI if (incr <= 0.0 && first < last) 153208987a5SXin LI errx(1, "needs positive increment"); 154208987a5SXin LI 155208987a5SXin LI if (incr >= 0.0 && first > last) 156208987a5SXin LI errx(1, "needs negative decrement"); 157208987a5SXin LI 158208987a5SXin LI if (fmt != NULL) { 159208987a5SXin LI if (!valid_format(fmt)) 160208987a5SXin LI errx(1, "invalid format string: `%s'", fmt); 161208987a5SXin LI fmt = unescape(fmt); 162208987a5SXin LI /* 163208987a5SXin LI * XXX to be bug for bug compatible with Plan 9 add a 164208987a5SXin LI * newline if none found at the end of the format string. 165208987a5SXin LI */ 166208987a5SXin LI } else 167208987a5SXin LI fmt = generate_format(first, incr, last, equalize, pad); 168208987a5SXin LI 169208987a5SXin LI if (incr > 0) { 170208987a5SXin LI for (; first <= last; first += incr) { 171208987a5SXin LI printf(fmt, first); 172208987a5SXin LI fputs(sep, stdout); 173208987a5SXin LI } 174208987a5SXin LI } else { 175208987a5SXin LI for (; first >= last; first += incr) { 176208987a5SXin LI printf(fmt, first); 177208987a5SXin LI fputs(sep, stdout); 178208987a5SXin LI } 179208987a5SXin LI } 180208987a5SXin LI if (term != NULL) 181208987a5SXin LI fputs(term, stdout); 182208987a5SXin LI 183208987a5SXin LI return (0); 184208987a5SXin LI } 185208987a5SXin LI 186208987a5SXin LI /* 187208987a5SXin LI * numeric - verify that string is numeric 188208987a5SXin LI */ 189208987a5SXin LI int 190208987a5SXin LI numeric(const char *s) 191208987a5SXin LI { 192208987a5SXin LI int seen_decimal_pt, decimal_pt_len; 193208987a5SXin LI 194208987a5SXin LI /* skip any sign */ 195208987a5SXin LI if (ISSIGN((unsigned char)*s)) 196208987a5SXin LI s++; 197208987a5SXin LI 198208987a5SXin LI seen_decimal_pt = 0; 199208987a5SXin LI decimal_pt_len = strlen(decimal_point); 200208987a5SXin LI while (*s) { 201208987a5SXin LI if (!isdigit((unsigned char)*s)) { 202208987a5SXin LI if (!seen_decimal_pt && 203208987a5SXin LI strncmp(s, decimal_point, decimal_pt_len) == 0) { 204208987a5SXin LI s += decimal_pt_len; 205208987a5SXin LI seen_decimal_pt = 1; 206208987a5SXin LI continue; 207208987a5SXin LI } 208208987a5SXin LI if (ISEXP((unsigned char)*s)) { 209208987a5SXin LI s++; 2100a167a9bSXin LI if (ISSIGN((unsigned char)*s) || 2110a167a9bSXin LI isdigit((unsigned char)*s)) { 212208987a5SXin LI s++; 213208987a5SXin LI continue; 214208987a5SXin LI } 215208987a5SXin LI } 216208987a5SXin LI break; 217208987a5SXin LI } 218208987a5SXin LI s++; 219208987a5SXin LI } 220208987a5SXin LI return (*s == '\0'); 221208987a5SXin LI } 222208987a5SXin LI 223208987a5SXin LI /* 224208987a5SXin LI * valid_format - validate user specified format string 225208987a5SXin LI */ 226208987a5SXin LI int 227208987a5SXin LI valid_format(const char *fmt) 228208987a5SXin LI { 229208987a5SXin LI int conversions = 0; 230208987a5SXin LI 231208987a5SXin LI while (*fmt != '\0') { 232208987a5SXin LI /* scan for conversions */ 233208987a5SXin LI if (*fmt != '\0' && *fmt != '%') { 234208987a5SXin LI do { 235208987a5SXin LI fmt++; 236208987a5SXin LI } while (*fmt != '\0' && *fmt != '%'); 237208987a5SXin LI } 238208987a5SXin LI /* scan a conversion */ 239208987a5SXin LI if (*fmt != '\0') { 240208987a5SXin LI do { 241208987a5SXin LI fmt++; 242208987a5SXin LI 243208987a5SXin LI /* ok %% */ 244208987a5SXin LI if (*fmt == '%') { 245208987a5SXin LI fmt++; 246208987a5SXin LI break; 247208987a5SXin LI } 248208987a5SXin LI /* valid conversions */ 249208987a5SXin LI if (strchr("eEfgG", *fmt) && 250208987a5SXin LI conversions++ < 1) { 251208987a5SXin LI fmt++; 252208987a5SXin LI break; 253208987a5SXin LI } 254208987a5SXin LI /* flags, width and precsision */ 255208987a5SXin LI if (isdigit((unsigned char)*fmt) || 256208987a5SXin LI strchr("+- 0#.", *fmt)) 257208987a5SXin LI continue; 258208987a5SXin LI 259208987a5SXin LI /* oops! bad conversion format! */ 260208987a5SXin LI return (0); 261208987a5SXin LI } while (*fmt != '\0'); 262208987a5SXin LI } 263208987a5SXin LI } 264208987a5SXin LI 265208987a5SXin LI return (conversions <= 1); 266208987a5SXin LI } 267208987a5SXin LI 268208987a5SXin LI /* 269208987a5SXin LI * unescape - handle C escapes in a string 270208987a5SXin LI */ 271208987a5SXin LI char * 272208987a5SXin LI unescape(char *orig) 273208987a5SXin LI { 274208987a5SXin LI char c, *cp, *new = orig; 275208987a5SXin LI int i; 276208987a5SXin LI 277208987a5SXin LI for (cp = orig; (*orig = *cp); cp++, orig++) { 278208987a5SXin LI if (*cp != '\\') 279208987a5SXin LI continue; 280208987a5SXin LI 281208987a5SXin LI switch (*++cp) { 282208987a5SXin LI case 'a': /* alert (bell) */ 283208987a5SXin LI *orig = '\a'; 284208987a5SXin LI continue; 285208987a5SXin LI case 'b': /* backspace */ 286208987a5SXin LI *orig = '\b'; 287208987a5SXin LI continue; 288208987a5SXin LI case 'e': /* escape */ 289208987a5SXin LI *orig = '\e'; 290208987a5SXin LI continue; 291208987a5SXin LI case 'f': /* formfeed */ 292208987a5SXin LI *orig = '\f'; 293208987a5SXin LI continue; 294208987a5SXin LI case 'n': /* newline */ 295208987a5SXin LI *orig = '\n'; 296208987a5SXin LI continue; 297208987a5SXin LI case 'r': /* carriage return */ 298208987a5SXin LI *orig = '\r'; 299208987a5SXin LI continue; 300208987a5SXin LI case 't': /* horizontal tab */ 301208987a5SXin LI *orig = '\t'; 302208987a5SXin LI continue; 303208987a5SXin LI case 'v': /* vertical tab */ 304208987a5SXin LI *orig = '\v'; 305208987a5SXin LI continue; 306208987a5SXin LI case '\\': /* backslash */ 307208987a5SXin LI *orig = '\\'; 308208987a5SXin LI continue; 309208987a5SXin LI case '\'': /* single quote */ 310208987a5SXin LI *orig = '\''; 311208987a5SXin LI continue; 312208987a5SXin LI case '\"': /* double quote */ 313208987a5SXin LI *orig = '"'; 314208987a5SXin LI continue; 315208987a5SXin LI case '0': 316208987a5SXin LI case '1': 317208987a5SXin LI case '2': 318208987a5SXin LI case '3': /* octal */ 319208987a5SXin LI case '4': 320208987a5SXin LI case '5': 321208987a5SXin LI case '6': 322208987a5SXin LI case '7': /* number */ 323208987a5SXin LI for (i = 0, c = 0; 324208987a5SXin LI ISODIGIT((unsigned char)*cp) && i < 3; 325208987a5SXin LI i++, cp++) { 326208987a5SXin LI c <<= 3; 327208987a5SXin LI c |= (*cp - '0'); 328208987a5SXin LI } 329208987a5SXin LI *orig = c; 330208987a5SXin LI --cp; 331208987a5SXin LI continue; 332208987a5SXin LI case 'x': /* hexidecimal number */ 333208987a5SXin LI cp++; /* skip 'x' */ 334208987a5SXin LI for (i = 0, c = 0; 335208987a5SXin LI isxdigit((unsigned char)*cp) && i < 2; 336208987a5SXin LI i++, cp++) { 337208987a5SXin LI c <<= 4; 338208987a5SXin LI if (isdigit((unsigned char)*cp)) 339208987a5SXin LI c |= (*cp - '0'); 340208987a5SXin LI else 341208987a5SXin LI c |= ((toupper((unsigned char)*cp) - 342208987a5SXin LI 'A') + 10); 343208987a5SXin LI } 344208987a5SXin LI *orig = c; 345208987a5SXin LI --cp; 346208987a5SXin LI continue; 347208987a5SXin LI default: 348208987a5SXin LI --cp; 349208987a5SXin LI break; 350208987a5SXin LI } 351208987a5SXin LI } 352208987a5SXin LI 353208987a5SXin LI return (new); 354208987a5SXin LI } 355208987a5SXin LI 356208987a5SXin LI /* 357208987a5SXin LI * e_atof - convert an ASCII string to a double 358208987a5SXin LI * exit if string is not a valid double, or if converted value would 359208987a5SXin LI * cause overflow or underflow 360208987a5SXin LI */ 361208987a5SXin LI double 362208987a5SXin LI e_atof(const char *num) 363208987a5SXin LI { 364208987a5SXin LI char *endp; 365208987a5SXin LI double dbl; 366208987a5SXin LI 367208987a5SXin LI errno = 0; 368208987a5SXin LI dbl = strtod(num, &endp); 369208987a5SXin LI 370208987a5SXin LI if (errno == ERANGE) 371208987a5SXin LI /* under or overflow */ 372208987a5SXin LI err(2, "%s", num); 373208987a5SXin LI else if (*endp != '\0') 374208987a5SXin LI /* "junk" left in number */ 375208987a5SXin LI errx(2, "invalid floating point argument: %s", num); 376208987a5SXin LI 377208987a5SXin LI /* zero shall have no sign */ 378208987a5SXin LI if (dbl == -0.0) 379208987a5SXin LI dbl = 0; 380208987a5SXin LI return (dbl); 381208987a5SXin LI } 382208987a5SXin LI 383208987a5SXin LI /* 384208987a5SXin LI * decimal_places - count decimal places in a number (string) 385208987a5SXin LI */ 386208987a5SXin LI int 387208987a5SXin LI decimal_places(const char *number) 388208987a5SXin LI { 389208987a5SXin LI int places = 0; 390208987a5SXin LI char *dp; 391208987a5SXin LI 392208987a5SXin LI /* look for a decimal point */ 393208987a5SXin LI if ((dp = strstr(number, decimal_point))) { 394208987a5SXin LI dp += strlen(decimal_point); 395208987a5SXin LI 396208987a5SXin LI while (isdigit((unsigned char)*dp++)) 397208987a5SXin LI places++; 398208987a5SXin LI } 399208987a5SXin LI return (places); 400208987a5SXin LI } 401208987a5SXin LI 402208987a5SXin LI /* 403208987a5SXin LI * generate_format - create a format string 404208987a5SXin LI * 405208987a5SXin LI * XXX to be bug for bug compatable with Plan9 and GNU return "%g" 406208987a5SXin LI * when "%g" prints as "%e" (this way no width adjustments are made) 407208987a5SXin LI */ 408208987a5SXin LI char * 409208987a5SXin LI generate_format(double first, double incr, double last, int equalize, char pad) 410208987a5SXin LI { 411208987a5SXin LI static char buf[256]; 412208987a5SXin LI char cc = '\0'; 413208987a5SXin LI int precision, width1, width2, places; 414208987a5SXin LI 415208987a5SXin LI if (equalize == 0) 416208987a5SXin LI return (default_format); 417208987a5SXin LI 418208987a5SXin LI /* figure out "last" value printed */ 419208987a5SXin LI if (first > last) 420208987a5SXin LI last = first - incr * floor((first - last) / incr); 421208987a5SXin LI else 422208987a5SXin LI last = first + incr * floor((last - first) / incr); 423208987a5SXin LI 424208987a5SXin LI sprintf(buf, "%g", incr); 425208987a5SXin LI if (strchr(buf, 'e')) 426208987a5SXin LI cc = 'e'; 427208987a5SXin LI precision = decimal_places(buf); 428208987a5SXin LI 429208987a5SXin LI width1 = sprintf(buf, "%g", first); 430208987a5SXin LI if (strchr(buf, 'e')) 431208987a5SXin LI cc = 'e'; 432208987a5SXin LI if ((places = decimal_places(buf))) 433208987a5SXin LI width1 -= (places + strlen(decimal_point)); 434208987a5SXin LI 435208987a5SXin LI precision = MAX(places, precision); 436208987a5SXin LI 437208987a5SXin LI width2 = sprintf(buf, "%g", last); 438208987a5SXin LI if (strchr(buf, 'e')) 439208987a5SXin LI cc = 'e'; 440208987a5SXin LI if ((places = decimal_places(buf))) 441208987a5SXin LI width2 -= (places + strlen(decimal_point)); 442208987a5SXin LI 443208987a5SXin LI if (precision) { 444208987a5SXin LI sprintf(buf, "%%%c%d.%d%c", pad, 445208987a5SXin LI MAX(width1, width2) + (int) strlen(decimal_point) + 446208987a5SXin LI precision, precision, (cc) ? cc : 'f'); 447208987a5SXin LI } else { 448208987a5SXin LI sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2), 449208987a5SXin LI (cc) ? cc : 'g'); 450208987a5SXin LI } 451208987a5SXin LI 452208987a5SXin LI return (buf); 453208987a5SXin LI } 454