1204103Sdelphij/* $NetBSD: seq.c,v 1.5 2008/07/21 14:19:26 lukem 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); 161204103Sdelphij /* 162204103Sdelphij * XXX to be bug for bug compatible with Plan 9 add a 163204103Sdelphij * newline if none found at the end of the format string. 164204103Sdelphij */ 165204103Sdelphij } else 166204103Sdelphij fmt = generate_format(first, incr, last, equalize, pad); 167204103Sdelphij 168204103Sdelphij if (incr > 0) { 169204103Sdelphij for (; first <= last; first += incr) { 170204103Sdelphij printf(fmt, first); 171204103Sdelphij fputs(sep, stdout); 172204103Sdelphij } 173204103Sdelphij } else { 174204103Sdelphij for (; first >= last; first += incr) { 175204103Sdelphij printf(fmt, first); 176204103Sdelphij fputs(sep, stdout); 177204103Sdelphij } 178204103Sdelphij } 179204103Sdelphij if (term != NULL) 180204103Sdelphij fputs(term, stdout); 181204103Sdelphij 182204103Sdelphij return (0); 183204103Sdelphij} 184204103Sdelphij 185204103Sdelphij/* 186204103Sdelphij * numeric - verify that string is numeric 187204103Sdelphij */ 188227182Sedstatic int 189204103Sdelphijnumeric(const char *s) 190204103Sdelphij{ 191204103Sdelphij int seen_decimal_pt, decimal_pt_len; 192204103Sdelphij 193204103Sdelphij /* skip any sign */ 194204103Sdelphij if (ISSIGN((unsigned char)*s)) 195204103Sdelphij s++; 196204103Sdelphij 197204103Sdelphij seen_decimal_pt = 0; 198204103Sdelphij decimal_pt_len = strlen(decimal_point); 199204103Sdelphij while (*s) { 200204103Sdelphij if (!isdigit((unsigned char)*s)) { 201204103Sdelphij if (!seen_decimal_pt && 202204103Sdelphij strncmp(s, decimal_point, decimal_pt_len) == 0) { 203204103Sdelphij s += decimal_pt_len; 204204103Sdelphij seen_decimal_pt = 1; 205204103Sdelphij continue; 206204103Sdelphij } 207204103Sdelphij if (ISEXP((unsigned char)*s)) { 208204103Sdelphij s++; 209204107Sdelphij if (ISSIGN((unsigned char)*s) || 210204107Sdelphij isdigit((unsigned char)*s)) { 211204103Sdelphij s++; 212204103Sdelphij continue; 213204103Sdelphij } 214204103Sdelphij } 215204103Sdelphij break; 216204103Sdelphij } 217204103Sdelphij s++; 218204103Sdelphij } 219204103Sdelphij return (*s == '\0'); 220204103Sdelphij} 221204103Sdelphij 222204103Sdelphij/* 223204103Sdelphij * valid_format - validate user specified format string 224204103Sdelphij */ 225227182Sedstatic int 226204103Sdelphijvalid_format(const char *fmt) 227204103Sdelphij{ 228204103Sdelphij int conversions = 0; 229204103Sdelphij 230204103Sdelphij while (*fmt != '\0') { 231204103Sdelphij /* scan for conversions */ 232204103Sdelphij if (*fmt != '\0' && *fmt != '%') { 233204103Sdelphij do { 234204103Sdelphij fmt++; 235204103Sdelphij } while (*fmt != '\0' && *fmt != '%'); 236204103Sdelphij } 237204103Sdelphij /* scan a conversion */ 238204103Sdelphij if (*fmt != '\0') { 239204103Sdelphij do { 240204103Sdelphij fmt++; 241204103Sdelphij 242204103Sdelphij /* ok %% */ 243204103Sdelphij if (*fmt == '%') { 244204103Sdelphij fmt++; 245204103Sdelphij break; 246204103Sdelphij } 247204103Sdelphij /* valid conversions */ 248204103Sdelphij if (strchr("eEfgG", *fmt) && 249204103Sdelphij conversions++ < 1) { 250204103Sdelphij fmt++; 251204103Sdelphij break; 252204103Sdelphij } 253215034Sbrucec /* flags, width and precision */ 254204103Sdelphij if (isdigit((unsigned char)*fmt) || 255204103Sdelphij strchr("+- 0#.", *fmt)) 256204103Sdelphij continue; 257204103Sdelphij 258204103Sdelphij /* oops! bad conversion format! */ 259204103Sdelphij return (0); 260204103Sdelphij } while (*fmt != '\0'); 261204103Sdelphij } 262204103Sdelphij } 263204103Sdelphij 264204103Sdelphij return (conversions <= 1); 265204103Sdelphij} 266204103Sdelphij 267204103Sdelphij/* 268204103Sdelphij * unescape - handle C escapes in a string 269204103Sdelphij */ 270227182Sedstatic char * 271204103Sdelphijunescape(char *orig) 272204103Sdelphij{ 273204103Sdelphij char c, *cp, *new = orig; 274204103Sdelphij int i; 275204103Sdelphij 276204103Sdelphij for (cp = orig; (*orig = *cp); cp++, orig++) { 277204103Sdelphij if (*cp != '\\') 278204103Sdelphij continue; 279204103Sdelphij 280204103Sdelphij switch (*++cp) { 281204103Sdelphij case 'a': /* alert (bell) */ 282204103Sdelphij *orig = '\a'; 283204103Sdelphij continue; 284204103Sdelphij case 'b': /* backspace */ 285204103Sdelphij *orig = '\b'; 286204103Sdelphij continue; 287204103Sdelphij case 'e': /* escape */ 288204103Sdelphij *orig = '\e'; 289204103Sdelphij continue; 290204103Sdelphij case 'f': /* formfeed */ 291204103Sdelphij *orig = '\f'; 292204103Sdelphij continue; 293204103Sdelphij case 'n': /* newline */ 294204103Sdelphij *orig = '\n'; 295204103Sdelphij continue; 296204103Sdelphij case 'r': /* carriage return */ 297204103Sdelphij *orig = '\r'; 298204103Sdelphij continue; 299204103Sdelphij case 't': /* horizontal tab */ 300204103Sdelphij *orig = '\t'; 301204103Sdelphij continue; 302204103Sdelphij case 'v': /* vertical tab */ 303204103Sdelphij *orig = '\v'; 304204103Sdelphij continue; 305204103Sdelphij case '\\': /* backslash */ 306204103Sdelphij *orig = '\\'; 307204103Sdelphij continue; 308204103Sdelphij case '\'': /* single quote */ 309204103Sdelphij *orig = '\''; 310204103Sdelphij continue; 311204103Sdelphij case '\"': /* double quote */ 312204103Sdelphij *orig = '"'; 313204103Sdelphij continue; 314204103Sdelphij case '0': 315204103Sdelphij case '1': 316204103Sdelphij case '2': 317204103Sdelphij case '3': /* octal */ 318204103Sdelphij case '4': 319204103Sdelphij case '5': 320204103Sdelphij case '6': 321204103Sdelphij case '7': /* number */ 322204103Sdelphij for (i = 0, c = 0; 323204103Sdelphij ISODIGIT((unsigned char)*cp) && i < 3; 324204103Sdelphij i++, cp++) { 325204103Sdelphij c <<= 3; 326204103Sdelphij c |= (*cp - '0'); 327204103Sdelphij } 328204103Sdelphij *orig = c; 329204103Sdelphij --cp; 330204103Sdelphij continue; 331215034Sbrucec case 'x': /* hexadecimal number */ 332204103Sdelphij cp++; /* skip 'x' */ 333204103Sdelphij for (i = 0, c = 0; 334204103Sdelphij isxdigit((unsigned char)*cp) && i < 2; 335204103Sdelphij i++, cp++) { 336204103Sdelphij c <<= 4; 337204103Sdelphij if (isdigit((unsigned char)*cp)) 338204103Sdelphij c |= (*cp - '0'); 339204103Sdelphij else 340204103Sdelphij c |= ((toupper((unsigned char)*cp) - 341204103Sdelphij 'A') + 10); 342204103Sdelphij } 343204103Sdelphij *orig = c; 344204103Sdelphij --cp; 345204103Sdelphij continue; 346204103Sdelphij default: 347204103Sdelphij --cp; 348204103Sdelphij break; 349204103Sdelphij } 350204103Sdelphij } 351204103Sdelphij 352204103Sdelphij return (new); 353204103Sdelphij} 354204103Sdelphij 355204103Sdelphij/* 356204103Sdelphij * e_atof - convert an ASCII string to a double 357204103Sdelphij * exit if string is not a valid double, or if converted value would 358204103Sdelphij * cause overflow or underflow 359204103Sdelphij */ 360227182Sedstatic double 361204103Sdelphije_atof(const char *num) 362204103Sdelphij{ 363204103Sdelphij char *endp; 364204103Sdelphij double dbl; 365204103Sdelphij 366204103Sdelphij errno = 0; 367204103Sdelphij dbl = strtod(num, &endp); 368204103Sdelphij 369204103Sdelphij if (errno == ERANGE) 370204103Sdelphij /* under or overflow */ 371204103Sdelphij err(2, "%s", num); 372204103Sdelphij else if (*endp != '\0') 373204103Sdelphij /* "junk" left in number */ 374204103Sdelphij errx(2, "invalid floating point argument: %s", num); 375204103Sdelphij 376204103Sdelphij /* zero shall have no sign */ 377204103Sdelphij if (dbl == -0.0) 378204103Sdelphij dbl = 0; 379204103Sdelphij return (dbl); 380204103Sdelphij} 381204103Sdelphij 382204103Sdelphij/* 383204103Sdelphij * decimal_places - count decimal places in a number (string) 384204103Sdelphij */ 385227182Sedstatic int 386204103Sdelphijdecimal_places(const char *number) 387204103Sdelphij{ 388204103Sdelphij int places = 0; 389204103Sdelphij char *dp; 390204103Sdelphij 391204103Sdelphij /* look for a decimal point */ 392204103Sdelphij if ((dp = strstr(number, decimal_point))) { 393204103Sdelphij dp += strlen(decimal_point); 394204103Sdelphij 395204103Sdelphij while (isdigit((unsigned char)*dp++)) 396204103Sdelphij places++; 397204103Sdelphij } 398204103Sdelphij return (places); 399204103Sdelphij} 400204103Sdelphij 401204103Sdelphij/* 402204103Sdelphij * generate_format - create a format string 403204103Sdelphij * 404215034Sbrucec * XXX to be bug for bug compatible with Plan9 and GNU return "%g" 405204103Sdelphij * when "%g" prints as "%e" (this way no width adjustments are made) 406204103Sdelphij */ 407227182Sedstatic char * 408204103Sdelphijgenerate_format(double first, double incr, double last, int equalize, char pad) 409204103Sdelphij{ 410204103Sdelphij static char buf[256]; 411204103Sdelphij char cc = '\0'; 412204103Sdelphij int precision, width1, width2, places; 413204103Sdelphij 414204103Sdelphij if (equalize == 0) 415204103Sdelphij return (default_format); 416204103Sdelphij 417204103Sdelphij /* figure out "last" value printed */ 418204103Sdelphij if (first > last) 419204103Sdelphij last = first - incr * floor((first - last) / incr); 420204103Sdelphij else 421204103Sdelphij last = first + incr * floor((last - first) / incr); 422204103Sdelphij 423204103Sdelphij sprintf(buf, "%g", incr); 424204103Sdelphij if (strchr(buf, 'e')) 425204103Sdelphij cc = 'e'; 426204103Sdelphij precision = decimal_places(buf); 427204103Sdelphij 428204103Sdelphij width1 = sprintf(buf, "%g", first); 429204103Sdelphij if (strchr(buf, 'e')) 430204103Sdelphij cc = 'e'; 431204103Sdelphij if ((places = decimal_places(buf))) 432204103Sdelphij width1 -= (places + strlen(decimal_point)); 433204103Sdelphij 434204103Sdelphij precision = MAX(places, precision); 435204103Sdelphij 436204103Sdelphij width2 = sprintf(buf, "%g", last); 437204103Sdelphij if (strchr(buf, 'e')) 438204103Sdelphij cc = 'e'; 439204103Sdelphij if ((places = decimal_places(buf))) 440204103Sdelphij width2 -= (places + strlen(decimal_point)); 441204103Sdelphij 442204103Sdelphij if (precision) { 443204103Sdelphij sprintf(buf, "%%%c%d.%d%c", pad, 444204103Sdelphij MAX(width1, width2) + (int) strlen(decimal_point) + 445204103Sdelphij precision, precision, (cc) ? cc : 'f'); 446204103Sdelphij } else { 447204103Sdelphij sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2), 448204103Sdelphij (cc) ? cc : 'g'); 449204103Sdelphij } 450204103Sdelphij 451204103Sdelphij return (buf); 452204103Sdelphij} 453