seq.c revision 1.15 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.15 2025/07/09 12:20:31 uwe 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 char default_format_fmt[] = { "%%.%uf" };
60 #define MAXPRECISION 40
61
62 /* Prototypes */
63
64 double e_atof(const char *);
65
66 int decimal_places(const char *);
67 int numeric(const char *);
68 int valid_format(const char *);
69
70 unsigned get_precision(const char *, unsigned);
71 char *generate_format(double, double, double, int, char, char *);
72 char *unescape(char *);
73
74 unsigned
75 get_precision(const char *number, unsigned minprec)
76 {
77 const char *p;
78 unsigned prec;
79
80 p = strstr(number, decimal_point);
81 if (p) {
82 prec = strlen(number) - (p - number);
83 if (prec > 0)
84 prec -= 1;
85 if (prec > MAXPRECISION)
86 prec = MAXPRECISION;
87 } else
88 prec = 0;
89
90 return prec < minprec ? minprec : prec;
91 }
92
93 /*
94 * The seq command will print out a numeric sequence from 1, the default,
95 * to a user specified upper limit by 1. The lower bound and increment
96 * maybe indicated by the user on the command line. The sequence can
97 * be either whole, the default, or decimal numbers.
98 */
99 int
100 main(int argc, char *argv[])
101 {
102 int c = 0, errflg = 0;
103 int equalize = 0;
104 unsigned prec;
105 unsigned maxprec = 0;
106 double first = 1.0;
107 double last = 0.0;
108 double incr = 0.0;
109 double prev;
110 struct lconv *locale;
111 char *fmt = NULL;
112 const char *sep = "\n";
113 const char *term = "\n";
114 char pad = ZERO;
115 char buf[6]; /* %.MAXPRECISIONf */
116
117
118 /* Determine the locale's decimal point. */
119 locale = localeconv();
120 if (locale && locale->decimal_point && locale->decimal_point[0] != '\0')
121 decimal_point = locale->decimal_point;
122
123 /*
124 * Process options, but handle negative numbers separately
125 * least they trip up getopt(3).
126 */
127 while ((optind < argc) && !numeric(argv[optind]) &&
128 (c = getopt(argc, argv, "f:hs:t:w")) != -1) {
129
130 switch (c) {
131 case 'f': /* format (plan9) */
132 fmt = optarg;
133 equalize = 0;
134 break;
135 case 's': /* separator (GNU) */
136 sep = unescape(optarg);
137 break;
138 case 't': /* terminator (new) */
139 term = unescape(optarg);
140 break;
141 case 'w': /* equal width (plan9) */
142 if (!fmt)
143 if (equalize++)
144 pad = SPACE;
145 break;
146 case 'h': /* help (GNU) */
147 default:
148 errflg++;
149 break;
150 }
151 }
152
153 argc -= optind;
154 argv += optind;
155 if (argc < 1 || argc > 3)
156 errflg++;
157
158 if (errflg) {
159 fprintf(stderr,
160 "usage: %s [-w] [-f format] [-s string] [-t string] [first [incr]] last\n",
161 getprogname());
162 exit(1);
163 }
164
165 last = e_atof(argv[argc - 1]);
166 maxprec = prec = get_precision(argv[argc - 1], 0);
167
168 if (argc > 1) {
169 first = e_atof(argv[0]);
170 prec = get_precision(argv[0], prec);
171 if (prec > maxprec)
172 maxprec = prec;
173 }
174
175 if (argc > 2) {
176 incr = e_atof(argv[1]);
177 prec = get_precision(argv[1], prec);
178 if (prec > maxprec)
179 maxprec = prec;
180 /* Plan 9/GNU don't do zero */
181 if (incr == 0.0)
182 errx(1, "zero %screment", (first < last)? "in" : "de");
183 }
184
185 /* default is one for Plan 9/GNU work alike */
186 if (incr == 0.0)
187 incr = (first < last) ? 1.0 : -1.0;
188
189 if (incr <= 0.0 && first < last)
190 errx(1, "needs positive increment");
191
192 if (incr >= 0.0 && first > last)
193 errx(1, "needs negative decrement");
194
195 if (fmt != NULL) {
196 if (!valid_format(fmt))
197 errx(1, "invalid format string: `%s'", fmt);
198 fmt = unescape(fmt);
199 if (!valid_format(fmt))
200 errx(1, "invalid format string");
201 /*
202 * XXX to be bug for bug compatible with Plan 9 add a
203 * newline if none found at the end of the format string.
204 */
205 } else {
206 if (maxprec == 0)
207 fmt = default_format;
208 else {
209 sprintf(buf, default_format_fmt, maxprec);
210 fmt = buf;
211 }
212 fmt = generate_format(first, incr, last, equalize, pad, fmt);
213 }
214
215 if (incr > 0) {
216 printf(fmt, first);
217 prev = first;
218 for (first += incr; first <= last; first += incr) {
219 if (first <= prev)
220 errx(1, "increment too small\n");
221 fputs(sep, stdout);
222 printf(fmt, first);
223 prev = first;
224 }
225 } else {
226 printf(fmt, first);
227 prev = first;
228 for (first += incr; first >= last; first += incr) {
229 if (first >= prev)
230 errx(1, "increment too small\n");
231 fputs(sep, stdout);
232 printf(fmt, first);
233 prev = first;
234 }
235 }
236 if (term != NULL)
237 fputs(term, stdout);
238
239 return (0);
240 }
241
242 /*
243 * numeric - verify that string is numeric
244 */
245 int
246 numeric(const char *s)
247 {
248 int seen_decimal_pt, decimal_pt_len;
249
250 /* skip any sign */
251 if (ISSIGN((unsigned char)*s))
252 s++;
253
254 seen_decimal_pt = 0;
255 decimal_pt_len = strlen(decimal_point);
256 while (*s) {
257 if (!isdigit((unsigned char)*s)) {
258 if (!seen_decimal_pt &&
259 strncmp(s, decimal_point, decimal_pt_len) == 0) {
260 s += decimal_pt_len;
261 seen_decimal_pt = 1;
262 continue;
263 }
264 if (ISEXP((unsigned char)*s)) {
265 s++;
266 /* optional sign */
267 if (ISSIGN((unsigned char)*s))
268 s++;
269 continue;
270 }
271 break;
272 }
273 s++;
274 }
275 return (*s == '\0');
276 }
277
278 /*
279 * valid_format - validate user specified format string
280 */
281 int
282 valid_format(const char *fmt)
283 {
284 unsigned conversions = 0;
285
286 while (*fmt != '\0') {
287 /* scan for conversions */
288 if (*fmt != '%') {
289 fmt++;
290 continue;
291 }
292 fmt++;
293
294 /* allow %% but not things like %10% */
295 if (*fmt == '%') {
296 fmt++;
297 continue;
298 }
299
300 /* flags */
301 while (*fmt != '\0' && strchr("#0- +'", *fmt)) {
302 fmt++;
303 }
304
305 /* field width */
306 while (*fmt != '\0' && strchr("0123456789", *fmt)) {
307 fmt++;
308 }
309
310 /* precision */
311 if (*fmt == '.') {
312 fmt++;
313 while (*fmt != '\0' && strchr("0123456789", *fmt)) {
314 fmt++;
315 }
316 }
317
318 /* conversion */
319 switch (*fmt) {
320 case 'A':
321 case 'a':
322 case 'E':
323 case 'e':
324 case 'F':
325 case 'f':
326 case 'G':
327 case 'g':
328 /* floating point formats are accepted */
329 conversions++;
330 break;
331 default:
332 /* anything else is not */
333 return 0;
334 }
335 }
336
337 return (conversions <= 1);
338 }
339
340 /*
341 * unescape - handle C escapes in a string
342 */
343 char *
344 unescape(char *orig)
345 {
346 char c, *cp, *new = orig;
347 int i;
348
349 for (cp = orig; (*orig = *cp); cp++, orig++) {
350 if (*cp != '\\')
351 continue;
352
353 switch (*++cp) {
354 case 'a': /* alert (bell) */
355 *orig = '\a';
356 continue;
357 case 'b': /* backspace */
358 *orig = '\b';
359 continue;
360 case 'e': /* escape */
361 *orig = '\x1B';
362 continue;
363 case 'f': /* formfeed */
364 *orig = '\f';
365 continue;
366 case 'n': /* newline */
367 *orig = '\n';
368 continue;
369 case 'r': /* carriage return */
370 *orig = '\r';
371 continue;
372 case 't': /* horizontal tab */
373 *orig = '\t';
374 continue;
375 case 'v': /* vertical tab */
376 *orig = '\v';
377 continue;
378 case '\\': /* backslash */
379 *orig = '\\';
380 continue;
381 case '\'': /* single quote */
382 *orig = '\'';
383 continue;
384 case '\"': /* double quote */
385 *orig = '"';
386 continue;
387 case '0':
388 case '1':
389 case '2':
390 case '3': /* octal */
391 case '4':
392 case '5':
393 case '6':
394 case '7': /* number */
395 for (i = 0, c = 0;
396 ISODIGIT((unsigned char)*cp) && i < 3;
397 i++, cp++) {
398 c <<= 3;
399 c |= (*cp - '0');
400 }
401 *orig = c;
402 --cp;
403 continue;
404 case 'x': /* hexadecimal number */
405 cp++; /* skip 'x' */
406 for (i = 0, c = 0;
407 isxdigit((unsigned char)*cp) && i < 2;
408 i++, cp++) {
409 c <<= 4;
410 if (isdigit((unsigned char)*cp))
411 c |= (*cp - '0');
412 else
413 c |= ((toupper((unsigned char)*cp) -
414 'A') + 10);
415 }
416 *orig = c;
417 --cp;
418 continue;
419 default:
420 --cp;
421 break;
422 }
423 }
424
425 return (new);
426 }
427
428 /*
429 * e_atof - convert an ASCII string to a double
430 * exit if string is not a valid double, or if converted value would
431 * cause overflow or underflow
432 */
433 double
434 e_atof(const char *num)
435 {
436 char *endp;
437 double dbl;
438
439 errno = 0;
440 dbl = strtod(num, &endp);
441
442 if (errno == ERANGE)
443 /* under or overflow */
444 err(2, "%s", num);
445 else if (*endp != '\0')
446 /* "junk" left in number */
447 errx(2, "invalid floating point argument: %s", num);
448
449 /* zero shall have no sign */
450 if (dbl == -0.0)
451 dbl = 0;
452 return (dbl);
453 }
454
455 /*
456 * decimal_places - count decimal places in a number (string)
457 */
458 int
459 decimal_places(const char *number)
460 {
461 int places = 0;
462 char *dp;
463
464 /* look for a decimal point */
465 if ((dp = strstr(number, decimal_point))) {
466 dp += strlen(decimal_point);
467
468 while (isdigit((unsigned char)*dp++))
469 places++;
470 }
471 return (places);
472 }
473
474 /*
475 * generate_format - create a format string
476 *
477 * XXX to be bug for bug compatible with Plan9 and GNU return "%g"
478 * when "%g" prints as "%e" (this way no width adjustments are made)
479 */
480 char *
481 generate_format(double first, double incr, double last,
482 int equalize, char pad, char *deffmt)
483 {
484 static char buf[256];
485 char cc = '\0';
486 int precision, width1, width2, places;
487
488 if (equalize == 0)
489 return deffmt;
490
491 /* figure out "last" value printed */
492 if (first > last)
493 last = first - incr * floor((first - last) / incr);
494 else
495 last = first + incr * floor((last - first) / incr);
496
497 sprintf(buf, deffmt, incr);
498 if (strchr(buf, 'e'))
499 cc = 'e';
500 precision = decimal_places(buf);
501
502 width1 = sprintf(buf, deffmt, first);
503 if (strchr(buf, 'e'))
504 cc = 'e';
505 if ((places = decimal_places(buf)))
506 width1 -= (places + strlen(decimal_point));
507
508 precision = MAX(places, precision);
509
510 width2 = sprintf(buf, deffmt, last);
511 if (strchr(buf, 'e'))
512 cc = 'e';
513 if ((places = decimal_places(buf)))
514 width2 -= (places + strlen(decimal_point));
515
516 if (precision) {
517 sprintf(buf, "%%%c%d.%d%c", pad,
518 MAX(width1, width2) + (int) strlen(decimal_point) +
519 precision, precision, (cc) ? cc : 'f');
520 } else {
521 sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2),
522 (cc) ? cc : 'g');
523 }
524
525 return (buf);
526 }
527