1 /* 2 * Copyright (c) 2005 The NetBSD Foundation, Inc. 3 * All rights reserved. 4 * 5 * This code is derived from software contributed to The NetBSD Foundation 6 * by Brian Ginsbach. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 * POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 #ifndef lint 32 __COPYRIGHT("@(#) Copyright (c) 2005\ 33 The NetBSD Foundation, Inc. All rights reserved."); 34 __RCSID("$NetBSD: seq.c,v 1.12 2021/03/20 22:10:17 cheusov Exp $"); 35 #endif /* not lint */ 36 37 #include <ctype.h> 38 #include <err.h> 39 #include <errno.h> 40 #include <math.h> 41 #include <locale.h> 42 #include <stdio.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <unistd.h> 46 47 #define ZERO '0' 48 #define SPACE ' ' 49 50 #define MAX(a, b) (((a) < (b))? (b) : (a)) 51 #define ISSIGN(c) ((int)(c) == '-' || (int)(c) == '+') 52 #define ISEXP(c) ((int)(c) == 'e' || (int)(c) == 'E') 53 #define ISODIGIT(c) ((int)(c) >= '0' && (int)(c) <= '7') 54 55 /* Globals */ 56 57 const char *decimal_point = "."; /* default */ 58 char default_format[] = { "%g" }; /* default */ 59 60 /* Prototypes */ 61 62 double e_atof(const char *); 63 64 int decimal_places(const char *); 65 int numeric(const char *); 66 int valid_format(const char *); 67 68 char *generate_format(double, double, double, int, char); 69 char *unescape(char *); 70 71 /* 72 * The seq command will print out a numeric sequence from 1, the default, 73 * to a user specified upper limit by 1. The lower bound and increment 74 * maybe indicated by the user on the command line. The sequence can 75 * be either whole, the default, or decimal numbers. 76 */ 77 int 78 main(int argc, char *argv[]) 79 { 80 int c = 0, errflg = 0; 81 int equalize = 0; 82 double first = 1.0; 83 double last = 0.0; 84 double incr = 0.0; 85 struct lconv *locale; 86 char *fmt = NULL; 87 const char *sep = "\n"; 88 const char *term = "\n"; 89 char pad = ZERO; 90 91 /* Determine the locale's decimal point. */ 92 locale = localeconv(); 93 if (locale && locale->decimal_point && locale->decimal_point[0] != '\0') 94 decimal_point = locale->decimal_point; 95 96 /* 97 * Process options, but handle negative numbers separately 98 * least they trip up getopt(3). 99 */ 100 while ((optind < argc) && !numeric(argv[optind]) && 101 (c = getopt(argc, argv, "f:hs:t:w")) != -1) { 102 103 switch (c) { 104 case 'f': /* format (plan9) */ 105 fmt = optarg; 106 equalize = 0; 107 break; 108 case 's': /* separator (GNU) */ 109 sep = unescape(optarg); 110 break; 111 case 't': /* terminator (new) */ 112 term = unescape(optarg); 113 break; 114 case 'w': /* equal width (plan9) */ 115 if (!fmt) 116 if (equalize++) 117 pad = SPACE; 118 break; 119 case 'h': /* help (GNU) */ 120 default: 121 errflg++; 122 break; 123 } 124 } 125 126 argc -= optind; 127 argv += optind; 128 if (argc < 1 || argc > 3) 129 errflg++; 130 131 if (errflg) { 132 fprintf(stderr, 133 "usage: %s [-w] [-f format] [-s string] [-t string] [first [incr]] last\n", 134 getprogname()); 135 exit(1); 136 } 137 138 last = e_atof(argv[argc - 1]); 139 140 if (argc > 1) 141 first = e_atof(argv[0]); 142 143 if (argc > 2) { 144 incr = e_atof(argv[1]); 145 /* Plan 9/GNU don't do zero */ 146 if (incr == 0.0) 147 errx(1, "zero %screment", (first < last)? "in" : "de"); 148 } 149 150 /* default is one for Plan 9/GNU work alike */ 151 if (incr == 0.0) 152 incr = (first < last) ? 1.0 : -1.0; 153 154 if (incr <= 0.0 && first < last) 155 errx(1, "needs positive increment"); 156 157 if (incr >= 0.0 && first > last) 158 errx(1, "needs negative decrement"); 159 160 if (fmt != NULL) { 161 if (!valid_format(fmt)) 162 errx(1, "invalid format string: `%s'", fmt); 163 fmt = unescape(fmt); 164 if (!valid_format(fmt)) 165 errx(1, "invalid format string"); 166 /* 167 * XXX to be bug for bug compatible with Plan 9 add a 168 * newline if none found at the end of the format string. 169 */ 170 } else 171 fmt = generate_format(first, incr, last, equalize, pad); 172 173 if (incr > 0) { 174 printf(fmt, first); 175 for (first += incr; first <= last; first += incr) { 176 fputs(sep, stdout); 177 printf(fmt, first); 178 } 179 } else { 180 printf(fmt, first); 181 for (first += incr; first >= last; first += incr) { 182 fputs(sep, stdout); 183 printf(fmt, first); 184 } 185 } 186 if (term != NULL) 187 fputs(term, stdout); 188 189 return (0); 190 } 191 192 /* 193 * numeric - verify that string is numeric 194 */ 195 int 196 numeric(const char *s) 197 { 198 int seen_decimal_pt, decimal_pt_len; 199 200 /* skip any sign */ 201 if (ISSIGN((unsigned char)*s)) 202 s++; 203 204 seen_decimal_pt = 0; 205 decimal_pt_len = strlen(decimal_point); 206 while (*s) { 207 if (!isdigit((unsigned char)*s)) { 208 if (!seen_decimal_pt && 209 strncmp(s, decimal_point, decimal_pt_len) == 0) { 210 s += decimal_pt_len; 211 seen_decimal_pt = 1; 212 continue; 213 } 214 if (ISEXP((unsigned char)*s)) { 215 s++; 216 /* optional sign */ 217 if (ISSIGN((unsigned char)*s)) 218 s++; 219 continue; 220 } 221 break; 222 } 223 s++; 224 } 225 return (*s == '\0'); 226 } 227 228 /* 229 * valid_format - validate user specified format string 230 */ 231 int 232 valid_format(const char *fmt) 233 { 234 unsigned conversions = 0; 235 236 while (*fmt != '\0') { 237 /* scan for conversions */ 238 if (*fmt != '%') { 239 fmt++; 240 continue; 241 } 242 fmt++; 243 244 /* allow %% but not things like %10% */ 245 if (*fmt == '%') { 246 fmt++; 247 continue; 248 } 249 250 /* flags */ 251 while (*fmt != '\0' && strchr("#0- +'", *fmt)) { 252 fmt++; 253 } 254 255 /* field width */ 256 while (*fmt != '\0' && strchr("0123456789", *fmt)) { 257 fmt++; 258 } 259 260 /* precision */ 261 if (*fmt == '.') { 262 fmt++; 263 while (*fmt != '\0' && strchr("0123456789", *fmt)) { 264 fmt++; 265 } 266 } 267 268 /* conversion */ 269 switch (*fmt) { 270 case 'A': 271 case 'a': 272 case 'E': 273 case 'e': 274 case 'F': 275 case 'f': 276 case 'G': 277 case 'g': 278 /* floating point formats are accepted */ 279 conversions++; 280 break; 281 default: 282 /* anything else is not */ 283 return 0; 284 } 285 } 286 287 return (conversions <= 1); 288 } 289 290 /* 291 * unescape - handle C escapes in a string 292 */ 293 char * 294 unescape(char *orig) 295 { 296 char c, *cp, *new = orig; 297 int i; 298 299 for (cp = orig; (*orig = *cp); cp++, orig++) { 300 if (*cp != '\\') 301 continue; 302 303 switch (*++cp) { 304 case 'a': /* alert (bell) */ 305 *orig = '\a'; 306 continue; 307 case 'b': /* backspace */ 308 *orig = '\b'; 309 continue; 310 case 'e': /* escape */ 311 *orig = '\x1B'; 312 continue; 313 case 'f': /* formfeed */ 314 *orig = '\f'; 315 continue; 316 case 'n': /* newline */ 317 *orig = '\n'; 318 continue; 319 case 'r': /* carriage return */ 320 *orig = '\r'; 321 continue; 322 case 't': /* horizontal tab */ 323 *orig = '\t'; 324 continue; 325 case 'v': /* vertical tab */ 326 *orig = '\v'; 327 continue; 328 case '\\': /* backslash */ 329 *orig = '\\'; 330 continue; 331 case '\'': /* single quote */ 332 *orig = '\''; 333 continue; 334 case '\"': /* double quote */ 335 *orig = '"'; 336 continue; 337 case '0': 338 case '1': 339 case '2': 340 case '3': /* octal */ 341 case '4': 342 case '5': 343 case '6': 344 case '7': /* number */ 345 for (i = 0, c = 0; 346 ISODIGIT((unsigned char)*cp) && i < 3; 347 i++, cp++) { 348 c <<= 3; 349 c |= (*cp - '0'); 350 } 351 *orig = c; 352 --cp; 353 continue; 354 case 'x': /* hexadecimal number */ 355 cp++; /* skip 'x' */ 356 for (i = 0, c = 0; 357 isxdigit((unsigned char)*cp) && i < 2; 358 i++, cp++) { 359 c <<= 4; 360 if (isdigit((unsigned char)*cp)) 361 c |= (*cp - '0'); 362 else 363 c |= ((toupper((unsigned char)*cp) - 364 'A') + 10); 365 } 366 *orig = c; 367 --cp; 368 continue; 369 default: 370 --cp; 371 break; 372 } 373 } 374 375 return (new); 376 } 377 378 /* 379 * e_atof - convert an ASCII string to a double 380 * exit if string is not a valid double, or if converted value would 381 * cause overflow or underflow 382 */ 383 double 384 e_atof(const char *num) 385 { 386 char *endp; 387 double dbl; 388 389 errno = 0; 390 dbl = strtod(num, &endp); 391 392 if (errno == ERANGE) 393 /* under or overflow */ 394 err(2, "%s", num); 395 else if (*endp != '\0') 396 /* "junk" left in number */ 397 errx(2, "invalid floating point argument: %s", num); 398 399 /* zero shall have no sign */ 400 if (dbl == -0.0) 401 dbl = 0; 402 return (dbl); 403 } 404 405 /* 406 * decimal_places - count decimal places in a number (string) 407 */ 408 int 409 decimal_places(const char *number) 410 { 411 int places = 0; 412 char *dp; 413 414 /* look for a decimal point */ 415 if ((dp = strstr(number, decimal_point))) { 416 dp += strlen(decimal_point); 417 418 while (isdigit((unsigned char)*dp++)) 419 places++; 420 } 421 return (places); 422 } 423 424 /* 425 * generate_format - create a format string 426 * 427 * XXX to be bug for bug compatible with Plan9 and GNU return "%g" 428 * when "%g" prints as "%e" (this way no width adjustments are made) 429 */ 430 char * 431 generate_format(double first, double incr, double last, int equalize, char pad) 432 { 433 static char buf[256]; 434 char cc = '\0'; 435 int precision, width1, width2, places; 436 437 if (equalize == 0) 438 return (default_format); 439 440 /* figure out "last" value printed */ 441 if (first > last) 442 last = first - incr * floor((first - last) / incr); 443 else 444 last = first + incr * floor((last - first) / incr); 445 446 sprintf(buf, "%g", incr); 447 if (strchr(buf, 'e')) 448 cc = 'e'; 449 precision = decimal_places(buf); 450 451 width1 = sprintf(buf, "%g", first); 452 if (strchr(buf, 'e')) 453 cc = 'e'; 454 if ((places = decimal_places(buf))) 455 width1 -= (places + strlen(decimal_point)); 456 457 precision = MAX(places, precision); 458 459 width2 = sprintf(buf, "%g", last); 460 if (strchr(buf, 'e')) 461 cc = 'e'; 462 if ((places = decimal_places(buf))) 463 width2 -= (places + strlen(decimal_point)); 464 465 if (precision) { 466 sprintf(buf, "%%%c%d.%d%c", pad, 467 MAX(width1, width2) + (int) strlen(decimal_point) + 468 precision, precision, (cc) ? cc : 'f'); 469 } else { 470 sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2), 471 (cc) ? cc : 'g'); 472 } 473 474 return (buf); 475 } 476