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