1277590Sdelphij/* $NetBSD: seq.c,v 1.7 2010/05/27 08:40:19 dholland Exp $ */ 2204103Sdelphij/* 3204103Sdelphij * Copyright (c) 2005 The NetBSD Foundation, Inc. 4204103Sdelphij * All rights reserved. 5204103Sdelphij * 6204103Sdelphij * This code is derived from software contributed to The NetBSD Foundation 7204103Sdelphij * by Brian Ginsbach. 8204103Sdelphij * 9204103Sdelphij * Redistribution and use in source and binary forms, with or without 10204103Sdelphij * modification, are permitted provided that the following conditions 11204103Sdelphij * are met: 12204103Sdelphij * 1. Redistributions of source code must retain the above copyright 13204103Sdelphij * notice, this list of conditions and the following disclaimer. 14204103Sdelphij * 2. Redistributions in binary form must reproduce the above copyright 15204103Sdelphij * notice, this list of conditions and the following disclaimer in the 16204103Sdelphij * documentation and/or other materials provided with the distribution. 17204103Sdelphij * 18204103Sdelphij * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 19204103Sdelphij * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20204103Sdelphij * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21204103Sdelphij * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 22204103Sdelphij * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23204103Sdelphij * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24204103Sdelphij * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25204103Sdelphij * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26204103Sdelphij * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27204103Sdelphij * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28204103Sdelphij * POSSIBILITY OF SUCH DAMAGE. 29204103Sdelphij */ 30204103Sdelphij 31204103Sdelphij#include <sys/cdefs.h> 32204103Sdelphij__FBSDID("$FreeBSD$"); 33204103Sdelphij 34204103Sdelphij#include <ctype.h> 35204103Sdelphij#include <err.h> 36204103Sdelphij#include <errno.h> 37204103Sdelphij#include <math.h> 38204103Sdelphij#include <locale.h> 39204103Sdelphij#include <stdio.h> 40204103Sdelphij#include <stdlib.h> 41204103Sdelphij#include <string.h> 42204103Sdelphij#include <unistd.h> 43204103Sdelphij 44204103Sdelphij#define ZERO '0' 45204103Sdelphij#define SPACE ' ' 46204103Sdelphij 47204103Sdelphij#define MAX(a, b) (((a) < (b))? (b) : (a)) 48204103Sdelphij#define ISSIGN(c) ((int)(c) == '-' || (int)(c) == '+') 49204103Sdelphij#define ISEXP(c) ((int)(c) == 'e' || (int)(c) == 'E') 50204103Sdelphij#define ISODIGIT(c) ((int)(c) >= '0' && (int)(c) <= '7') 51204103Sdelphij 52204103Sdelphij/* Globals */ 53204103Sdelphij 54227182Sedstatic const char *decimal_point = "."; /* default */ 55227182Sedstatic char default_format[] = { "%g" }; /* default */ 56204103Sdelphij 57204103Sdelphij/* Prototypes */ 58204103Sdelphij 59227182Sedstatic double e_atof(const char *); 60204103Sdelphij 61227182Sedstatic int decimal_places(const char *); 62227182Sedstatic int numeric(const char *); 63227182Sedstatic int valid_format(const char *); 64204103Sdelphij 65227182Sedstatic char *generate_format(double, double, double, int, char); 66227182Sedstatic char *unescape(char *); 67204103Sdelphij 68204103Sdelphij/* 69204103Sdelphij * The seq command will print out a numeric sequence from 1, the default, 70204103Sdelphij * to a user specified upper limit by 1. The lower bound and increment 71204103Sdelphij * maybe indicated by the user on the command line. The sequence can 72204103Sdelphij * be either whole, the default, or decimal numbers. 73204103Sdelphij */ 74204103Sdelphijint 75204103Sdelphijmain(int argc, char *argv[]) 76204103Sdelphij{ 77204103Sdelphij int c = 0, errflg = 0; 78204103Sdelphij int equalize = 0; 79204103Sdelphij double first = 1.0; 80204103Sdelphij double last = 0.0; 81204103Sdelphij double incr = 0.0; 82204103Sdelphij struct lconv *locale; 83204103Sdelphij char *fmt = NULL; 84204103Sdelphij const char *sep = "\n"; 85204103Sdelphij const char *term = NULL; 86204103Sdelphij char pad = ZERO; 87204103Sdelphij 88204103Sdelphij /* Determine the locale's decimal point. */ 89204103Sdelphij locale = localeconv(); 90204103Sdelphij if (locale && locale->decimal_point && locale->decimal_point[0] != '\0') 91204103Sdelphij decimal_point = locale->decimal_point; 92204103Sdelphij 93204103Sdelphij /* 94204103Sdelphij * Process options, but handle negative numbers separately 95204103Sdelphij * least they trip up getopt(3). 96204103Sdelphij */ 97204103Sdelphij while ((optind < argc) && !numeric(argv[optind]) && 98204103Sdelphij (c = getopt(argc, argv, "f:hs:t:w")) != -1) { 99204103Sdelphij 100204103Sdelphij switch (c) { 101204103Sdelphij case 'f': /* format (plan9) */ 102204103Sdelphij fmt = optarg; 103204103Sdelphij equalize = 0; 104204103Sdelphij break; 105204103Sdelphij case 's': /* separator (GNU) */ 106204103Sdelphij sep = unescape(optarg); 107204103Sdelphij break; 108204103Sdelphij case 't': /* terminator (new) */ 109204103Sdelphij term = unescape(optarg); 110204103Sdelphij break; 111204103Sdelphij case 'w': /* equal width (plan9) */ 112204103Sdelphij if (!fmt) 113204103Sdelphij if (equalize++) 114204103Sdelphij pad = SPACE; 115204103Sdelphij break; 116204103Sdelphij case 'h': /* help (GNU) */ 117204103Sdelphij default: 118204103Sdelphij errflg++; 119204103Sdelphij break; 120204103Sdelphij } 121204103Sdelphij } 122204103Sdelphij 123204103Sdelphij argc -= optind; 124204103Sdelphij argv += optind; 125204103Sdelphij if (argc < 1 || argc > 3) 126204103Sdelphij errflg++; 127204103Sdelphij 128204103Sdelphij if (errflg) { 129204103Sdelphij fprintf(stderr, 130204103Sdelphij "usage: %s [-w] [-f format] [-s string] [-t string] [first [incr]] last\n", 131204103Sdelphij getprogname()); 132204103Sdelphij exit(1); 133204103Sdelphij } 134204103Sdelphij 135204103Sdelphij last = e_atof(argv[argc - 1]); 136204103Sdelphij 137204103Sdelphij if (argc > 1) 138204103Sdelphij first = e_atof(argv[0]); 139204103Sdelphij 140204103Sdelphij if (argc > 2) { 141204103Sdelphij incr = e_atof(argv[1]); 142204103Sdelphij /* Plan 9/GNU don't do zero */ 143204103Sdelphij if (incr == 0.0) 144204103Sdelphij errx(1, "zero %screment", (first < last)? "in" : "de"); 145204103Sdelphij } 146204103Sdelphij 147204103Sdelphij /* default is one for Plan 9/GNU work alike */ 148204103Sdelphij if (incr == 0.0) 149204103Sdelphij incr = (first < last) ? 1.0 : -1.0; 150204103Sdelphij 151204103Sdelphij if (incr <= 0.0 && first < last) 152204103Sdelphij errx(1, "needs positive increment"); 153204103Sdelphij 154204103Sdelphij if (incr >= 0.0 && first > last) 155204103Sdelphij errx(1, "needs negative decrement"); 156204103Sdelphij 157204103Sdelphij if (fmt != NULL) { 158204103Sdelphij if (!valid_format(fmt)) 159204103Sdelphij errx(1, "invalid format string: `%s'", fmt); 160204103Sdelphij fmt = unescape(fmt); 161277590Sdelphij if (!valid_format(fmt)) 162277590Sdelphij errx(1, "invalid format string"); 163204103Sdelphij /* 164204103Sdelphij * XXX to be bug for bug compatible with Plan 9 add a 165204103Sdelphij * newline if none found at the end of the format string. 166204103Sdelphij */ 167204103Sdelphij } else 168204103Sdelphij fmt = generate_format(first, incr, last, equalize, pad); 169204103Sdelphij 170204103Sdelphij if (incr > 0) { 171204103Sdelphij for (; first <= last; first += incr) { 172204103Sdelphij printf(fmt, first); 173204103Sdelphij fputs(sep, stdout); 174204103Sdelphij } 175204103Sdelphij } else { 176204103Sdelphij for (; first >= last; first += incr) { 177204103Sdelphij printf(fmt, first); 178204103Sdelphij fputs(sep, stdout); 179204103Sdelphij } 180204103Sdelphij } 181204103Sdelphij if (term != NULL) 182204103Sdelphij fputs(term, stdout); 183204103Sdelphij 184204103Sdelphij return (0); 185204103Sdelphij} 186204103Sdelphij 187204103Sdelphij/* 188204103Sdelphij * numeric - verify that string is numeric 189204103Sdelphij */ 190227182Sedstatic int 191204103Sdelphijnumeric(const char *s) 192204103Sdelphij{ 193204103Sdelphij int seen_decimal_pt, decimal_pt_len; 194204103Sdelphij 195204103Sdelphij /* skip any sign */ 196204103Sdelphij if (ISSIGN((unsigned char)*s)) 197204103Sdelphij s++; 198204103Sdelphij 199204103Sdelphij seen_decimal_pt = 0; 200204103Sdelphij decimal_pt_len = strlen(decimal_point); 201204103Sdelphij while (*s) { 202204103Sdelphij if (!isdigit((unsigned char)*s)) { 203204103Sdelphij if (!seen_decimal_pt && 204204103Sdelphij strncmp(s, decimal_point, decimal_pt_len) == 0) { 205204103Sdelphij s += decimal_pt_len; 206204103Sdelphij seen_decimal_pt = 1; 207204103Sdelphij continue; 208204103Sdelphij } 209204103Sdelphij if (ISEXP((unsigned char)*s)) { 210204103Sdelphij s++; 211204107Sdelphij if (ISSIGN((unsigned char)*s) || 212204107Sdelphij isdigit((unsigned char)*s)) { 213204103Sdelphij s++; 214204103Sdelphij continue; 215204103Sdelphij } 216204103Sdelphij } 217204103Sdelphij break; 218204103Sdelphij } 219204103Sdelphij s++; 220204103Sdelphij } 221204103Sdelphij return (*s == '\0'); 222204103Sdelphij} 223204103Sdelphij 224204103Sdelphij/* 225204103Sdelphij * valid_format - validate user specified format string 226204103Sdelphij */ 227227182Sedstatic int 228204103Sdelphijvalid_format(const char *fmt) 229204103Sdelphij{ 230277590Sdelphij unsigned conversions = 0; 231204103Sdelphij 232204103Sdelphij while (*fmt != '\0') { 233204103Sdelphij /* scan for conversions */ 234277590Sdelphij if (*fmt != '%') { 235277590Sdelphij fmt++; 236277590Sdelphij continue; 237204103Sdelphij } 238277590Sdelphij fmt++; 239204103Sdelphij 240277590Sdelphij /* allow %% but not things like %10% */ 241277590Sdelphij if (*fmt == '%') { 242277590Sdelphij fmt++; 243277590Sdelphij continue; 244277590Sdelphij } 245204103Sdelphij 246277590Sdelphij /* flags */ 247277590Sdelphij while (*fmt != '\0' && strchr("#0- +'", *fmt)) { 248277590Sdelphij fmt++; 249204103Sdelphij } 250277590Sdelphij 251277590Sdelphij /* field width */ 252277590Sdelphij while (*fmt != '\0' && strchr("0123456789", *fmt)) { 253277590Sdelphij fmt++; 254277590Sdelphij } 255277590Sdelphij 256277590Sdelphij /* precision */ 257277590Sdelphij if (*fmt == '.') { 258277590Sdelphij fmt++; 259277590Sdelphij while (*fmt != '\0' && strchr("0123456789", *fmt)) { 260277590Sdelphij fmt++; 261277590Sdelphij } 262277590Sdelphij } 263277590Sdelphij 264277590Sdelphij /* conversion */ 265277590Sdelphij switch (*fmt) { 266277590Sdelphij case 'A': 267277590Sdelphij case 'a': 268277590Sdelphij case 'E': 269277590Sdelphij case 'e': 270277590Sdelphij case 'F': 271277590Sdelphij case 'f': 272277590Sdelphij case 'G': 273277590Sdelphij case 'g': 274277590Sdelphij /* floating point formats are accepted */ 275277590Sdelphij conversions++; 276277590Sdelphij break; 277277590Sdelphij default: 278277590Sdelphij /* anything else is not */ 279277590Sdelphij return 0; 280277590Sdelphij } 281204103Sdelphij } 282204103Sdelphij 283204103Sdelphij return (conversions <= 1); 284204103Sdelphij} 285204103Sdelphij 286204103Sdelphij/* 287204103Sdelphij * unescape - handle C escapes in a string 288204103Sdelphij */ 289227182Sedstatic char * 290204103Sdelphijunescape(char *orig) 291204103Sdelphij{ 292204103Sdelphij char c, *cp, *new = orig; 293204103Sdelphij int i; 294204103Sdelphij 295204103Sdelphij for (cp = orig; (*orig = *cp); cp++, orig++) { 296204103Sdelphij if (*cp != '\\') 297204103Sdelphij continue; 298204103Sdelphij 299204103Sdelphij switch (*++cp) { 300204103Sdelphij case 'a': /* alert (bell) */ 301204103Sdelphij *orig = '\a'; 302204103Sdelphij continue; 303204103Sdelphij case 'b': /* backspace */ 304204103Sdelphij *orig = '\b'; 305204103Sdelphij continue; 306204103Sdelphij case 'e': /* escape */ 307204103Sdelphij *orig = '\e'; 308204103Sdelphij continue; 309204103Sdelphij case 'f': /* formfeed */ 310204103Sdelphij *orig = '\f'; 311204103Sdelphij continue; 312204103Sdelphij case 'n': /* newline */ 313204103Sdelphij *orig = '\n'; 314204103Sdelphij continue; 315204103Sdelphij case 'r': /* carriage return */ 316204103Sdelphij *orig = '\r'; 317204103Sdelphij continue; 318204103Sdelphij case 't': /* horizontal tab */ 319204103Sdelphij *orig = '\t'; 320204103Sdelphij continue; 321204103Sdelphij case 'v': /* vertical tab */ 322204103Sdelphij *orig = '\v'; 323204103Sdelphij continue; 324204103Sdelphij case '\\': /* backslash */ 325204103Sdelphij *orig = '\\'; 326204103Sdelphij continue; 327204103Sdelphij case '\'': /* single quote */ 328204103Sdelphij *orig = '\''; 329204103Sdelphij continue; 330204103Sdelphij case '\"': /* double quote */ 331204103Sdelphij *orig = '"'; 332204103Sdelphij continue; 333204103Sdelphij case '0': 334204103Sdelphij case '1': 335204103Sdelphij case '2': 336204103Sdelphij case '3': /* octal */ 337204103Sdelphij case '4': 338204103Sdelphij case '5': 339204103Sdelphij case '6': 340204103Sdelphij case '7': /* number */ 341204103Sdelphij for (i = 0, c = 0; 342204103Sdelphij ISODIGIT((unsigned char)*cp) && i < 3; 343204103Sdelphij i++, cp++) { 344204103Sdelphij c <<= 3; 345204103Sdelphij c |= (*cp - '0'); 346204103Sdelphij } 347204103Sdelphij *orig = c; 348204103Sdelphij --cp; 349204103Sdelphij continue; 350215034Sbrucec case 'x': /* hexadecimal number */ 351204103Sdelphij cp++; /* skip 'x' */ 352204103Sdelphij for (i = 0, c = 0; 353204103Sdelphij isxdigit((unsigned char)*cp) && i < 2; 354204103Sdelphij i++, cp++) { 355204103Sdelphij c <<= 4; 356204103Sdelphij if (isdigit((unsigned char)*cp)) 357204103Sdelphij c |= (*cp - '0'); 358204103Sdelphij else 359204103Sdelphij c |= ((toupper((unsigned char)*cp) - 360204103Sdelphij 'A') + 10); 361204103Sdelphij } 362204103Sdelphij *orig = c; 363204103Sdelphij --cp; 364204103Sdelphij continue; 365204103Sdelphij default: 366204103Sdelphij --cp; 367204103Sdelphij break; 368204103Sdelphij } 369204103Sdelphij } 370204103Sdelphij 371204103Sdelphij return (new); 372204103Sdelphij} 373204103Sdelphij 374204103Sdelphij/* 375204103Sdelphij * e_atof - convert an ASCII string to a double 376204103Sdelphij * exit if string is not a valid double, or if converted value would 377204103Sdelphij * cause overflow or underflow 378204103Sdelphij */ 379227182Sedstatic double 380204103Sdelphije_atof(const char *num) 381204103Sdelphij{ 382204103Sdelphij char *endp; 383204103Sdelphij double dbl; 384204103Sdelphij 385204103Sdelphij errno = 0; 386204103Sdelphij dbl = strtod(num, &endp); 387204103Sdelphij 388204103Sdelphij if (errno == ERANGE) 389204103Sdelphij /* under or overflow */ 390204103Sdelphij err(2, "%s", num); 391204103Sdelphij else if (*endp != '\0') 392204103Sdelphij /* "junk" left in number */ 393204103Sdelphij errx(2, "invalid floating point argument: %s", num); 394204103Sdelphij 395204103Sdelphij /* zero shall have no sign */ 396204103Sdelphij if (dbl == -0.0) 397204103Sdelphij dbl = 0; 398204103Sdelphij return (dbl); 399204103Sdelphij} 400204103Sdelphij 401204103Sdelphij/* 402204103Sdelphij * decimal_places - count decimal places in a number (string) 403204103Sdelphij */ 404227182Sedstatic int 405204103Sdelphijdecimal_places(const char *number) 406204103Sdelphij{ 407204103Sdelphij int places = 0; 408204103Sdelphij char *dp; 409204103Sdelphij 410204103Sdelphij /* look for a decimal point */ 411204103Sdelphij if ((dp = strstr(number, decimal_point))) { 412204103Sdelphij dp += strlen(decimal_point); 413204103Sdelphij 414204103Sdelphij while (isdigit((unsigned char)*dp++)) 415204103Sdelphij places++; 416204103Sdelphij } 417204103Sdelphij return (places); 418204103Sdelphij} 419204103Sdelphij 420204103Sdelphij/* 421204103Sdelphij * generate_format - create a format string 422204103Sdelphij * 423215034Sbrucec * XXX to be bug for bug compatible with Plan9 and GNU return "%g" 424204103Sdelphij * when "%g" prints as "%e" (this way no width adjustments are made) 425204103Sdelphij */ 426227182Sedstatic char * 427204103Sdelphijgenerate_format(double first, double incr, double last, int equalize, char pad) 428204103Sdelphij{ 429204103Sdelphij static char buf[256]; 430204103Sdelphij char cc = '\0'; 431204103Sdelphij int precision, width1, width2, places; 432204103Sdelphij 433204103Sdelphij if (equalize == 0) 434204103Sdelphij return (default_format); 435204103Sdelphij 436204103Sdelphij /* figure out "last" value printed */ 437204103Sdelphij if (first > last) 438204103Sdelphij last = first - incr * floor((first - last) / incr); 439204103Sdelphij else 440204103Sdelphij last = first + incr * floor((last - first) / incr); 441204103Sdelphij 442204103Sdelphij sprintf(buf, "%g", incr); 443204103Sdelphij if (strchr(buf, 'e')) 444204103Sdelphij cc = 'e'; 445204103Sdelphij precision = decimal_places(buf); 446204103Sdelphij 447204103Sdelphij width1 = sprintf(buf, "%g", first); 448204103Sdelphij if (strchr(buf, 'e')) 449204103Sdelphij cc = 'e'; 450204103Sdelphij if ((places = decimal_places(buf))) 451204103Sdelphij width1 -= (places + strlen(decimal_point)); 452204103Sdelphij 453204103Sdelphij precision = MAX(places, precision); 454204103Sdelphij 455204103Sdelphij width2 = sprintf(buf, "%g", last); 456204103Sdelphij if (strchr(buf, 'e')) 457204103Sdelphij cc = 'e'; 458204103Sdelphij if ((places = decimal_places(buf))) 459204103Sdelphij width2 -= (places + strlen(decimal_point)); 460204103Sdelphij 461204103Sdelphij if (precision) { 462204103Sdelphij sprintf(buf, "%%%c%d.%d%c", pad, 463204103Sdelphij MAX(width1, width2) + (int) strlen(decimal_point) + 464204103Sdelphij precision, precision, (cc) ? cc : 'f'); 465204103Sdelphij } else { 466204103Sdelphij sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2), 467204103Sdelphij (cc) ? cc : 'g'); 468204103Sdelphij } 469204103Sdelphij 470204103Sdelphij return (buf); 471204103Sdelphij} 472