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 54204103Sdelphijconst char *decimal_point = "."; /* default */ 55204103Sdelphijchar default_format[] = { "%g" }; /* default */ 56204103Sdelphij 57204103Sdelphij/* Prototypes */ 58204103Sdelphij 59204103Sdelphijdouble e_atof(const char *); 60204103Sdelphij 61204103Sdelphijint decimal_places(const char *); 62204103Sdelphijint main(int, char *[]); 63204103Sdelphijint numeric(const char *); 64204103Sdelphijint valid_format(const char *); 65204103Sdelphij 66204103Sdelphijchar *generate_format(double, double, double, int, char); 67204103Sdelphijchar *unescape(char *); 68204103Sdelphij 69204103Sdelphij/* 70204103Sdelphij * The seq command will print out a numeric sequence from 1, the default, 71204103Sdelphij * to a user specified upper limit by 1. The lower bound and increment 72204103Sdelphij * maybe indicated by the user on the command line. The sequence can 73204103Sdelphij * be either whole, the default, or decimal numbers. 74204103Sdelphij */ 75204103Sdelphijint 76204103Sdelphijmain(int argc, char *argv[]) 77204103Sdelphij{ 78204103Sdelphij int c = 0, errflg = 0; 79204103Sdelphij int equalize = 0; 80204103Sdelphij double first = 1.0; 81204103Sdelphij double last = 0.0; 82204103Sdelphij double incr = 0.0; 83204103Sdelphij struct lconv *locale; 84204103Sdelphij char *fmt = NULL; 85204103Sdelphij const char *sep = "\n"; 86204103Sdelphij const char *term = NULL; 87204103Sdelphij char pad = ZERO; 88204103Sdelphij 89204103Sdelphij /* Determine the locale's decimal point. */ 90204103Sdelphij locale = localeconv(); 91204103Sdelphij if (locale && locale->decimal_point && locale->decimal_point[0] != '\0') 92204103Sdelphij decimal_point = locale->decimal_point; 93204103Sdelphij 94204103Sdelphij /* 95204103Sdelphij * Process options, but handle negative numbers separately 96204103Sdelphij * least they trip up getopt(3). 97204103Sdelphij */ 98204103Sdelphij while ((optind < argc) && !numeric(argv[optind]) && 99204103Sdelphij (c = getopt(argc, argv, "f:hs:t:w")) != -1) { 100204103Sdelphij 101204103Sdelphij switch (c) { 102204103Sdelphij case 'f': /* format (plan9) */ 103204103Sdelphij fmt = optarg; 104204103Sdelphij equalize = 0; 105204103Sdelphij break; 106204103Sdelphij case 's': /* separator (GNU) */ 107204103Sdelphij sep = unescape(optarg); 108204103Sdelphij break; 109204103Sdelphij case 't': /* terminator (new) */ 110204103Sdelphij term = unescape(optarg); 111204103Sdelphij break; 112204103Sdelphij case 'w': /* equal width (plan9) */ 113204103Sdelphij if (!fmt) 114204103Sdelphij if (equalize++) 115204103Sdelphij pad = SPACE; 116204103Sdelphij break; 117204103Sdelphij case 'h': /* help (GNU) */ 118204103Sdelphij default: 119204103Sdelphij errflg++; 120204103Sdelphij break; 121204103Sdelphij } 122204103Sdelphij } 123204103Sdelphij 124204103Sdelphij argc -= optind; 125204103Sdelphij argv += optind; 126204103Sdelphij if (argc < 1 || argc > 3) 127204103Sdelphij errflg++; 128204103Sdelphij 129204103Sdelphij if (errflg) { 130204103Sdelphij fprintf(stderr, 131204103Sdelphij "usage: %s [-w] [-f format] [-s string] [-t string] [first [incr]] last\n", 132204103Sdelphij getprogname()); 133204103Sdelphij exit(1); 134204103Sdelphij } 135204103Sdelphij 136204103Sdelphij last = e_atof(argv[argc - 1]); 137204103Sdelphij 138204103Sdelphij if (argc > 1) 139204103Sdelphij first = e_atof(argv[0]); 140204103Sdelphij 141204103Sdelphij if (argc > 2) { 142204103Sdelphij incr = e_atof(argv[1]); 143204103Sdelphij /* Plan 9/GNU don't do zero */ 144204103Sdelphij if (incr == 0.0) 145204103Sdelphij errx(1, "zero %screment", (first < last)? "in" : "de"); 146204103Sdelphij } 147204103Sdelphij 148204103Sdelphij /* default is one for Plan 9/GNU work alike */ 149204103Sdelphij if (incr == 0.0) 150204103Sdelphij incr = (first < last) ? 1.0 : -1.0; 151204103Sdelphij 152204103Sdelphij if (incr <= 0.0 && first < last) 153204103Sdelphij errx(1, "needs positive increment"); 154204103Sdelphij 155204103Sdelphij if (incr >= 0.0 && first > last) 156204103Sdelphij errx(1, "needs negative decrement"); 157204103Sdelphij 158204103Sdelphij if (fmt != NULL) { 159204103Sdelphij if (!valid_format(fmt)) 160204103Sdelphij errx(1, "invalid format string: `%s'", fmt); 161204103Sdelphij fmt = unescape(fmt); 162204103Sdelphij /* 163204103Sdelphij * XXX to be bug for bug compatible with Plan 9 add a 164204103Sdelphij * newline if none found at the end of the format string. 165204103Sdelphij */ 166204103Sdelphij } else 167204103Sdelphij fmt = generate_format(first, incr, last, equalize, pad); 168204103Sdelphij 169204103Sdelphij if (incr > 0) { 170204103Sdelphij for (; first <= last; first += incr) { 171204103Sdelphij printf(fmt, first); 172204103Sdelphij fputs(sep, stdout); 173204103Sdelphij } 174204103Sdelphij } else { 175204103Sdelphij for (; first >= last; first += incr) { 176204103Sdelphij printf(fmt, first); 177204103Sdelphij fputs(sep, stdout); 178204103Sdelphij } 179204103Sdelphij } 180204103Sdelphij if (term != NULL) 181204103Sdelphij fputs(term, stdout); 182204103Sdelphij 183204103Sdelphij return (0); 184204103Sdelphij} 185204103Sdelphij 186204103Sdelphij/* 187204103Sdelphij * numeric - verify that string is numeric 188204103Sdelphij */ 189204103Sdelphijint 190204103Sdelphijnumeric(const char *s) 191204103Sdelphij{ 192204103Sdelphij int seen_decimal_pt, decimal_pt_len; 193204103Sdelphij 194204103Sdelphij /* skip any sign */ 195204103Sdelphij if (ISSIGN((unsigned char)*s)) 196204103Sdelphij s++; 197204103Sdelphij 198204103Sdelphij seen_decimal_pt = 0; 199204103Sdelphij decimal_pt_len = strlen(decimal_point); 200204103Sdelphij while (*s) { 201204103Sdelphij if (!isdigit((unsigned char)*s)) { 202204103Sdelphij if (!seen_decimal_pt && 203204103Sdelphij strncmp(s, decimal_point, decimal_pt_len) == 0) { 204204103Sdelphij s += decimal_pt_len; 205204103Sdelphij seen_decimal_pt = 1; 206204103Sdelphij continue; 207204103Sdelphij } 208204103Sdelphij if (ISEXP((unsigned char)*s)) { 209204103Sdelphij s++; 210204107Sdelphij if (ISSIGN((unsigned char)*s) || 211204107Sdelphij isdigit((unsigned char)*s)) { 212204103Sdelphij s++; 213204103Sdelphij continue; 214204103Sdelphij } 215204103Sdelphij } 216204103Sdelphij break; 217204103Sdelphij } 218204103Sdelphij s++; 219204103Sdelphij } 220204103Sdelphij return (*s == '\0'); 221204103Sdelphij} 222204103Sdelphij 223204103Sdelphij/* 224204103Sdelphij * valid_format - validate user specified format string 225204103Sdelphij */ 226204103Sdelphijint 227204103Sdelphijvalid_format(const char *fmt) 228204103Sdelphij{ 229204103Sdelphij int conversions = 0; 230204103Sdelphij 231204103Sdelphij while (*fmt != '\0') { 232204103Sdelphij /* scan for conversions */ 233204103Sdelphij if (*fmt != '\0' && *fmt != '%') { 234204103Sdelphij do { 235204103Sdelphij fmt++; 236204103Sdelphij } while (*fmt != '\0' && *fmt != '%'); 237204103Sdelphij } 238204103Sdelphij /* scan a conversion */ 239204103Sdelphij if (*fmt != '\0') { 240204103Sdelphij do { 241204103Sdelphij fmt++; 242204103Sdelphij 243204103Sdelphij /* ok %% */ 244204103Sdelphij if (*fmt == '%') { 245204103Sdelphij fmt++; 246204103Sdelphij break; 247204103Sdelphij } 248204103Sdelphij /* valid conversions */ 249204103Sdelphij if (strchr("eEfgG", *fmt) && 250204103Sdelphij conversions++ < 1) { 251204103Sdelphij fmt++; 252204103Sdelphij break; 253204103Sdelphij } 254215034Sbrucec /* flags, width and precision */ 255204103Sdelphij if (isdigit((unsigned char)*fmt) || 256204103Sdelphij strchr("+- 0#.", *fmt)) 257204103Sdelphij continue; 258204103Sdelphij 259204103Sdelphij /* oops! bad conversion format! */ 260204103Sdelphij return (0); 261204103Sdelphij } while (*fmt != '\0'); 262204103Sdelphij } 263204103Sdelphij } 264204103Sdelphij 265204103Sdelphij return (conversions <= 1); 266204103Sdelphij} 267204103Sdelphij 268204103Sdelphij/* 269204103Sdelphij * unescape - handle C escapes in a string 270204103Sdelphij */ 271204103Sdelphijchar * 272204103Sdelphijunescape(char *orig) 273204103Sdelphij{ 274204103Sdelphij char c, *cp, *new = orig; 275204103Sdelphij int i; 276204103Sdelphij 277204103Sdelphij for (cp = orig; (*orig = *cp); cp++, orig++) { 278204103Sdelphij if (*cp != '\\') 279204103Sdelphij continue; 280204103Sdelphij 281204103Sdelphij switch (*++cp) { 282204103Sdelphij case 'a': /* alert (bell) */ 283204103Sdelphij *orig = '\a'; 284204103Sdelphij continue; 285204103Sdelphij case 'b': /* backspace */ 286204103Sdelphij *orig = '\b'; 287204103Sdelphij continue; 288204103Sdelphij case 'e': /* escape */ 289204103Sdelphij *orig = '\e'; 290204103Sdelphij continue; 291204103Sdelphij case 'f': /* formfeed */ 292204103Sdelphij *orig = '\f'; 293204103Sdelphij continue; 294204103Sdelphij case 'n': /* newline */ 295204103Sdelphij *orig = '\n'; 296204103Sdelphij continue; 297204103Sdelphij case 'r': /* carriage return */ 298204103Sdelphij *orig = '\r'; 299204103Sdelphij continue; 300204103Sdelphij case 't': /* horizontal tab */ 301204103Sdelphij *orig = '\t'; 302204103Sdelphij continue; 303204103Sdelphij case 'v': /* vertical tab */ 304204103Sdelphij *orig = '\v'; 305204103Sdelphij continue; 306204103Sdelphij case '\\': /* backslash */ 307204103Sdelphij *orig = '\\'; 308204103Sdelphij continue; 309204103Sdelphij case '\'': /* single quote */ 310204103Sdelphij *orig = '\''; 311204103Sdelphij continue; 312204103Sdelphij case '\"': /* double quote */ 313204103Sdelphij *orig = '"'; 314204103Sdelphij continue; 315204103Sdelphij case '0': 316204103Sdelphij case '1': 317204103Sdelphij case '2': 318204103Sdelphij case '3': /* octal */ 319204103Sdelphij case '4': 320204103Sdelphij case '5': 321204103Sdelphij case '6': 322204103Sdelphij case '7': /* number */ 323204103Sdelphij for (i = 0, c = 0; 324204103Sdelphij ISODIGIT((unsigned char)*cp) && i < 3; 325204103Sdelphij i++, cp++) { 326204103Sdelphij c <<= 3; 327204103Sdelphij c |= (*cp - '0'); 328204103Sdelphij } 329204103Sdelphij *orig = c; 330204103Sdelphij --cp; 331204103Sdelphij continue; 332215034Sbrucec case 'x': /* hexadecimal number */ 333204103Sdelphij cp++; /* skip 'x' */ 334204103Sdelphij for (i = 0, c = 0; 335204103Sdelphij isxdigit((unsigned char)*cp) && i < 2; 336204103Sdelphij i++, cp++) { 337204103Sdelphij c <<= 4; 338204103Sdelphij if (isdigit((unsigned char)*cp)) 339204103Sdelphij c |= (*cp - '0'); 340204103Sdelphij else 341204103Sdelphij c |= ((toupper((unsigned char)*cp) - 342204103Sdelphij 'A') + 10); 343204103Sdelphij } 344204103Sdelphij *orig = c; 345204103Sdelphij --cp; 346204103Sdelphij continue; 347204103Sdelphij default: 348204103Sdelphij --cp; 349204103Sdelphij break; 350204103Sdelphij } 351204103Sdelphij } 352204103Sdelphij 353204103Sdelphij return (new); 354204103Sdelphij} 355204103Sdelphij 356204103Sdelphij/* 357204103Sdelphij * e_atof - convert an ASCII string to a double 358204103Sdelphij * exit if string is not a valid double, or if converted value would 359204103Sdelphij * cause overflow or underflow 360204103Sdelphij */ 361204103Sdelphijdouble 362204103Sdelphije_atof(const char *num) 363204103Sdelphij{ 364204103Sdelphij char *endp; 365204103Sdelphij double dbl; 366204103Sdelphij 367204103Sdelphij errno = 0; 368204103Sdelphij dbl = strtod(num, &endp); 369204103Sdelphij 370204103Sdelphij if (errno == ERANGE) 371204103Sdelphij /* under or overflow */ 372204103Sdelphij err(2, "%s", num); 373204103Sdelphij else if (*endp != '\0') 374204103Sdelphij /* "junk" left in number */ 375204103Sdelphij errx(2, "invalid floating point argument: %s", num); 376204103Sdelphij 377204103Sdelphij /* zero shall have no sign */ 378204103Sdelphij if (dbl == -0.0) 379204103Sdelphij dbl = 0; 380204103Sdelphij return (dbl); 381204103Sdelphij} 382204103Sdelphij 383204103Sdelphij/* 384204103Sdelphij * decimal_places - count decimal places in a number (string) 385204103Sdelphij */ 386204103Sdelphijint 387204103Sdelphijdecimal_places(const char *number) 388204103Sdelphij{ 389204103Sdelphij int places = 0; 390204103Sdelphij char *dp; 391204103Sdelphij 392204103Sdelphij /* look for a decimal point */ 393204103Sdelphij if ((dp = strstr(number, decimal_point))) { 394204103Sdelphij dp += strlen(decimal_point); 395204103Sdelphij 396204103Sdelphij while (isdigit((unsigned char)*dp++)) 397204103Sdelphij places++; 398204103Sdelphij } 399204103Sdelphij return (places); 400204103Sdelphij} 401204103Sdelphij 402204103Sdelphij/* 403204103Sdelphij * generate_format - create a format string 404204103Sdelphij * 405215034Sbrucec * XXX to be bug for bug compatible with Plan9 and GNU return "%g" 406204103Sdelphij * when "%g" prints as "%e" (this way no width adjustments are made) 407204103Sdelphij */ 408204103Sdelphijchar * 409204103Sdelphijgenerate_format(double first, double incr, double last, int equalize, char pad) 410204103Sdelphij{ 411204103Sdelphij static char buf[256]; 412204103Sdelphij char cc = '\0'; 413204103Sdelphij int precision, width1, width2, places; 414204103Sdelphij 415204103Sdelphij if (equalize == 0) 416204103Sdelphij return (default_format); 417204103Sdelphij 418204103Sdelphij /* figure out "last" value printed */ 419204103Sdelphij if (first > last) 420204103Sdelphij last = first - incr * floor((first - last) / incr); 421204103Sdelphij else 422204103Sdelphij last = first + incr * floor((last - first) / incr); 423204103Sdelphij 424204103Sdelphij sprintf(buf, "%g", incr); 425204103Sdelphij if (strchr(buf, 'e')) 426204103Sdelphij cc = 'e'; 427204103Sdelphij precision = decimal_places(buf); 428204103Sdelphij 429204103Sdelphij width1 = sprintf(buf, "%g", first); 430204103Sdelphij if (strchr(buf, 'e')) 431204103Sdelphij cc = 'e'; 432204103Sdelphij if ((places = decimal_places(buf))) 433204103Sdelphij width1 -= (places + strlen(decimal_point)); 434204103Sdelphij 435204103Sdelphij precision = MAX(places, precision); 436204103Sdelphij 437204103Sdelphij width2 = sprintf(buf, "%g", last); 438204103Sdelphij if (strchr(buf, 'e')) 439204103Sdelphij cc = 'e'; 440204103Sdelphij if ((places = decimal_places(buf))) 441204103Sdelphij width2 -= (places + strlen(decimal_point)); 442204103Sdelphij 443204103Sdelphij if (precision) { 444204103Sdelphij sprintf(buf, "%%%c%d.%d%c", pad, 445204103Sdelphij MAX(width1, width2) + (int) strlen(decimal_point) + 446204103Sdelphij precision, precision, (cc) ? cc : 'f'); 447204103Sdelphij } else { 448204103Sdelphij sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2), 449204103Sdelphij (cc) ? cc : 'g'); 450204103Sdelphij } 451204103Sdelphij 452204103Sdelphij return (buf); 453204103Sdelphij} 454