xref: /freebsd/usr.bin/seq/seq.c (revision 0a167a9b98fa8a9cca77a6f6c09bcb366579d1d5)
1208987a5SXin LI /*	$NetBSD: seq.c,v 1.5 2008/07/21 14:19:26 lukem Exp $	*/
2208987a5SXin LI /*
3208987a5SXin LI  * Copyright (c) 2005 The NetBSD Foundation, Inc.
4208987a5SXin LI  * All rights reserved.
5208987a5SXin LI  *
6208987a5SXin LI  * This code is derived from software contributed to The NetBSD Foundation
7208987a5SXin LI  * by Brian Ginsbach.
8208987a5SXin LI  *
9208987a5SXin LI  * Redistribution and use in source and binary forms, with or without
10208987a5SXin LI  * modification, are permitted provided that the following conditions
11208987a5SXin LI  * are met:
12208987a5SXin LI  * 1. Redistributions of source code must retain the above copyright
13208987a5SXin LI  *    notice, this list of conditions and the following disclaimer.
14208987a5SXin LI  * 2. Redistributions in binary form must reproduce the above copyright
15208987a5SXin LI  *    notice, this list of conditions and the following disclaimer in the
16208987a5SXin LI  *    documentation and/or other materials provided with the distribution.
17208987a5SXin LI  *
18208987a5SXin LI  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
19208987a5SXin LI  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20208987a5SXin LI  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21208987a5SXin LI  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
22208987a5SXin LI  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23208987a5SXin LI  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24208987a5SXin LI  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25208987a5SXin LI  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26208987a5SXin LI  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27208987a5SXin LI  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28208987a5SXin LI  * POSSIBILITY OF SUCH DAMAGE.
29208987a5SXin LI  */
30208987a5SXin LI 
31208987a5SXin LI #include <sys/cdefs.h>
32208987a5SXin LI __FBSDID("$FreeBSD$");
33208987a5SXin LI 
34208987a5SXin LI #include <ctype.h>
35208987a5SXin LI #include <err.h>
36208987a5SXin LI #include <errno.h>
37208987a5SXin LI #include <math.h>
38208987a5SXin LI #include <locale.h>
39208987a5SXin LI #include <stdio.h>
40208987a5SXin LI #include <stdlib.h>
41208987a5SXin LI #include <string.h>
42208987a5SXin LI #include <unistd.h>
43208987a5SXin LI 
44208987a5SXin LI #define ZERO	'0'
45208987a5SXin LI #define SPACE	' '
46208987a5SXin LI 
47208987a5SXin LI #define MAX(a, b)	(((a) < (b))? (b) : (a))
48208987a5SXin LI #define ISSIGN(c)	((int)(c) == '-' || (int)(c) == '+')
49208987a5SXin LI #define ISEXP(c)	((int)(c) == 'e' || (int)(c) == 'E')
50208987a5SXin LI #define ISODIGIT(c)	((int)(c) >= '0' && (int)(c) <= '7')
51208987a5SXin LI 
52208987a5SXin LI /* Globals */
53208987a5SXin LI 
54208987a5SXin LI const char *decimal_point = ".";	/* default */
55208987a5SXin LI char default_format[] = { "%g" };	/* default */
56208987a5SXin LI 
57208987a5SXin LI /* Prototypes */
58208987a5SXin LI 
59208987a5SXin LI double e_atof(const char *);
60208987a5SXin LI 
61208987a5SXin LI int decimal_places(const char *);
62208987a5SXin LI int main(int, char *[]);
63208987a5SXin LI int numeric(const char *);
64208987a5SXin LI int valid_format(const char *);
65208987a5SXin LI 
66208987a5SXin LI char *generate_format(double, double, double, int, char);
67208987a5SXin LI char *unescape(char *);
68208987a5SXin LI 
69208987a5SXin LI /*
70208987a5SXin LI  * The seq command will print out a numeric sequence from 1, the default,
71208987a5SXin LI  * to a user specified upper limit by 1.  The lower bound and increment
72208987a5SXin LI  * maybe indicated by the user on the command line.  The sequence can
73208987a5SXin LI  * be either whole, the default, or decimal numbers.
74208987a5SXin LI  */
75208987a5SXin LI int
76208987a5SXin LI main(int argc, char *argv[])
77208987a5SXin LI {
78208987a5SXin LI 	int c = 0, errflg = 0;
79208987a5SXin LI 	int equalize = 0;
80208987a5SXin LI 	double first = 1.0;
81208987a5SXin LI 	double last = 0.0;
82208987a5SXin LI 	double incr = 0.0;
83208987a5SXin LI 	struct lconv *locale;
84208987a5SXin LI 	char *fmt = NULL;
85208987a5SXin LI 	const char *sep = "\n";
86208987a5SXin LI 	const char *term = NULL;
87208987a5SXin LI 	char pad = ZERO;
88208987a5SXin LI 
89208987a5SXin LI 	/* Determine the locale's decimal point. */
90208987a5SXin LI 	locale = localeconv();
91208987a5SXin LI 	if (locale && locale->decimal_point && locale->decimal_point[0] != '\0')
92208987a5SXin LI 		decimal_point = locale->decimal_point;
93208987a5SXin LI 
94208987a5SXin LI 	/*
95208987a5SXin LI          * Process options, but handle negative numbers separately
96208987a5SXin LI          * least they trip up getopt(3).
97208987a5SXin LI          */
98208987a5SXin LI 	while ((optind < argc) && !numeric(argv[optind]) &&
99208987a5SXin LI 	    (c = getopt(argc, argv, "f:hs:t:w")) != -1) {
100208987a5SXin LI 
101208987a5SXin LI 		switch (c) {
102208987a5SXin LI 		case 'f':	/* format (plan9) */
103208987a5SXin LI 			fmt = optarg;
104208987a5SXin LI 			equalize = 0;
105208987a5SXin LI 			break;
106208987a5SXin LI 		case 's':	/* separator (GNU) */
107208987a5SXin LI 			sep = unescape(optarg);
108208987a5SXin LI 			break;
109208987a5SXin LI 		case 't':	/* terminator (new) */
110208987a5SXin LI 			term = unescape(optarg);
111208987a5SXin LI 			break;
112208987a5SXin LI 		case 'w':	/* equal width (plan9) */
113208987a5SXin LI 			if (!fmt)
114208987a5SXin LI 				if (equalize++)
115208987a5SXin LI 					pad = SPACE;
116208987a5SXin LI 			break;
117208987a5SXin LI 		case 'h':	/* help (GNU) */
118208987a5SXin LI 		default:
119208987a5SXin LI 			errflg++;
120208987a5SXin LI 			break;
121208987a5SXin LI 		}
122208987a5SXin LI 	}
123208987a5SXin LI 
124208987a5SXin LI 	argc -= optind;
125208987a5SXin LI 	argv += optind;
126208987a5SXin LI 	if (argc < 1 || argc > 3)
127208987a5SXin LI 		errflg++;
128208987a5SXin LI 
129208987a5SXin LI 	if (errflg) {
130208987a5SXin LI 		fprintf(stderr,
131208987a5SXin LI 		    "usage: %s [-w] [-f format] [-s string] [-t string] [first [incr]] last\n",
132208987a5SXin LI 		    getprogname());
133208987a5SXin LI 		exit(1);
134208987a5SXin LI 	}
135208987a5SXin LI 
136208987a5SXin LI 	last = e_atof(argv[argc - 1]);
137208987a5SXin LI 
138208987a5SXin LI 	if (argc > 1)
139208987a5SXin LI 		first = e_atof(argv[0]);
140208987a5SXin LI 
141208987a5SXin LI 	if (argc > 2) {
142208987a5SXin LI 		incr = e_atof(argv[1]);
143208987a5SXin LI 		/* Plan 9/GNU don't do zero */
144208987a5SXin LI 		if (incr == 0.0)
145208987a5SXin LI 			errx(1, "zero %screment", (first < last)? "in" : "de");
146208987a5SXin LI 	}
147208987a5SXin LI 
148208987a5SXin LI 	/* default is one for Plan 9/GNU work alike */
149208987a5SXin LI 	if (incr == 0.0)
150208987a5SXin LI 		incr = (first < last) ? 1.0 : -1.0;
151208987a5SXin LI 
152208987a5SXin LI 	if (incr <= 0.0 && first < last)
153208987a5SXin LI 		errx(1, "needs positive increment");
154208987a5SXin LI 
155208987a5SXin LI 	if (incr >= 0.0 && first > last)
156208987a5SXin LI 		errx(1, "needs negative decrement");
157208987a5SXin LI 
158208987a5SXin LI 	if (fmt != NULL) {
159208987a5SXin LI 		if (!valid_format(fmt))
160208987a5SXin LI 			errx(1, "invalid format string: `%s'", fmt);
161208987a5SXin LI 		fmt = unescape(fmt);
162208987a5SXin LI 		/*
163208987a5SXin LI 	         * XXX to be bug for bug compatible with Plan 9 add a
164208987a5SXin LI 		 * newline if none found at the end of the format string.
165208987a5SXin LI 		 */
166208987a5SXin LI 	} else
167208987a5SXin LI 		fmt = generate_format(first, incr, last, equalize, pad);
168208987a5SXin LI 
169208987a5SXin LI 	if (incr > 0) {
170208987a5SXin LI 		for (; first <= last; first += incr) {
171208987a5SXin LI 			printf(fmt, first);
172208987a5SXin LI 			fputs(sep, stdout);
173208987a5SXin LI 		}
174208987a5SXin LI 	} else {
175208987a5SXin LI 		for (; first >= last; first += incr) {
176208987a5SXin LI 			printf(fmt, first);
177208987a5SXin LI 			fputs(sep, stdout);
178208987a5SXin LI 		}
179208987a5SXin LI 	}
180208987a5SXin LI 	if (term != NULL)
181208987a5SXin LI 		fputs(term, stdout);
182208987a5SXin LI 
183208987a5SXin LI 	return (0);
184208987a5SXin LI }
185208987a5SXin LI 
186208987a5SXin LI /*
187208987a5SXin LI  * numeric - verify that string is numeric
188208987a5SXin LI  */
189208987a5SXin LI int
190208987a5SXin LI numeric(const char *s)
191208987a5SXin LI {
192208987a5SXin LI 	int seen_decimal_pt, decimal_pt_len;
193208987a5SXin LI 
194208987a5SXin LI 	/* skip any sign */
195208987a5SXin LI 	if (ISSIGN((unsigned char)*s))
196208987a5SXin LI 		s++;
197208987a5SXin LI 
198208987a5SXin LI 	seen_decimal_pt = 0;
199208987a5SXin LI 	decimal_pt_len = strlen(decimal_point);
200208987a5SXin LI 	while (*s) {
201208987a5SXin LI 		if (!isdigit((unsigned char)*s)) {
202208987a5SXin LI 			if (!seen_decimal_pt &&
203208987a5SXin LI 			    strncmp(s, decimal_point, decimal_pt_len) == 0) {
204208987a5SXin LI 				s += decimal_pt_len;
205208987a5SXin LI 				seen_decimal_pt = 1;
206208987a5SXin LI 				continue;
207208987a5SXin LI 			}
208208987a5SXin LI 			if (ISEXP((unsigned char)*s)) {
209208987a5SXin LI 				s++;
2100a167a9bSXin LI 				if (ISSIGN((unsigned char)*s) ||
2110a167a9bSXin LI 				    isdigit((unsigned char)*s)) {
212208987a5SXin LI 					s++;
213208987a5SXin LI 					continue;
214208987a5SXin LI 				}
215208987a5SXin LI 			}
216208987a5SXin LI 			break;
217208987a5SXin LI 		}
218208987a5SXin LI 		s++;
219208987a5SXin LI 	}
220208987a5SXin LI 	return (*s == '\0');
221208987a5SXin LI }
222208987a5SXin LI 
223208987a5SXin LI /*
224208987a5SXin LI  * valid_format - validate user specified format string
225208987a5SXin LI  */
226208987a5SXin LI int
227208987a5SXin LI valid_format(const char *fmt)
228208987a5SXin LI {
229208987a5SXin LI 	int conversions = 0;
230208987a5SXin LI 
231208987a5SXin LI 	while (*fmt != '\0') {
232208987a5SXin LI 		/* scan for conversions */
233208987a5SXin LI 		if (*fmt != '\0' && *fmt != '%') {
234208987a5SXin LI 			do {
235208987a5SXin LI 				fmt++;
236208987a5SXin LI 			} while (*fmt != '\0' && *fmt != '%');
237208987a5SXin LI 		}
238208987a5SXin LI 		/* scan a conversion */
239208987a5SXin LI 		if (*fmt != '\0') {
240208987a5SXin LI 			do {
241208987a5SXin LI 				fmt++;
242208987a5SXin LI 
243208987a5SXin LI 				/* ok %% */
244208987a5SXin LI 				if (*fmt == '%') {
245208987a5SXin LI 					fmt++;
246208987a5SXin LI 					break;
247208987a5SXin LI 				}
248208987a5SXin LI 				/* valid conversions */
249208987a5SXin LI 				if (strchr("eEfgG", *fmt) &&
250208987a5SXin LI 				    conversions++ < 1) {
251208987a5SXin LI 					fmt++;
252208987a5SXin LI 					break;
253208987a5SXin LI 				}
254208987a5SXin LI 				/* flags, width and precsision */
255208987a5SXin LI 				if (isdigit((unsigned char)*fmt) ||
256208987a5SXin LI 				    strchr("+- 0#.", *fmt))
257208987a5SXin LI 					continue;
258208987a5SXin LI 
259208987a5SXin LI 				/* oops! bad conversion format! */
260208987a5SXin LI 				return (0);
261208987a5SXin LI 			} while (*fmt != '\0');
262208987a5SXin LI 		}
263208987a5SXin LI 	}
264208987a5SXin LI 
265208987a5SXin LI 	return (conversions <= 1);
266208987a5SXin LI }
267208987a5SXin LI 
268208987a5SXin LI /*
269208987a5SXin LI  * unescape - handle C escapes in a string
270208987a5SXin LI  */
271208987a5SXin LI char *
272208987a5SXin LI unescape(char *orig)
273208987a5SXin LI {
274208987a5SXin LI 	char c, *cp, *new = orig;
275208987a5SXin LI 	int i;
276208987a5SXin LI 
277208987a5SXin LI 	for (cp = orig; (*orig = *cp); cp++, orig++) {
278208987a5SXin LI 		if (*cp != '\\')
279208987a5SXin LI 			continue;
280208987a5SXin LI 
281208987a5SXin LI 		switch (*++cp) {
282208987a5SXin LI 		case 'a':	/* alert (bell) */
283208987a5SXin LI 			*orig = '\a';
284208987a5SXin LI 			continue;
285208987a5SXin LI 		case 'b':	/* backspace */
286208987a5SXin LI 			*orig = '\b';
287208987a5SXin LI 			continue;
288208987a5SXin LI 		case 'e':	/* escape */
289208987a5SXin LI 			*orig = '\e';
290208987a5SXin LI 			continue;
291208987a5SXin LI 		case 'f':	/* formfeed */
292208987a5SXin LI 			*orig = '\f';
293208987a5SXin LI 			continue;
294208987a5SXin LI 		case 'n':	/* newline */
295208987a5SXin LI 			*orig = '\n';
296208987a5SXin LI 			continue;
297208987a5SXin LI 		case 'r':	/* carriage return */
298208987a5SXin LI 			*orig = '\r';
299208987a5SXin LI 			continue;
300208987a5SXin LI 		case 't':	/* horizontal tab */
301208987a5SXin LI 			*orig = '\t';
302208987a5SXin LI 			continue;
303208987a5SXin LI 		case 'v':	/* vertical tab */
304208987a5SXin LI 			*orig = '\v';
305208987a5SXin LI 			continue;
306208987a5SXin LI 		case '\\':	/* backslash */
307208987a5SXin LI 			*orig = '\\';
308208987a5SXin LI 			continue;
309208987a5SXin LI 		case '\'':	/* single quote */
310208987a5SXin LI 			*orig = '\'';
311208987a5SXin LI 			continue;
312208987a5SXin LI 		case '\"':	/* double quote */
313208987a5SXin LI 			*orig = '"';
314208987a5SXin LI 			continue;
315208987a5SXin LI 		case '0':
316208987a5SXin LI 		case '1':
317208987a5SXin LI 		case '2':
318208987a5SXin LI 		case '3':	/* octal */
319208987a5SXin LI 		case '4':
320208987a5SXin LI 		case '5':
321208987a5SXin LI 		case '6':
322208987a5SXin LI 		case '7':	/* number */
323208987a5SXin LI 			for (i = 0, c = 0;
324208987a5SXin LI 			     ISODIGIT((unsigned char)*cp) && i < 3;
325208987a5SXin LI 			     i++, cp++) {
326208987a5SXin LI 				c <<= 3;
327208987a5SXin LI 				c |= (*cp - '0');
328208987a5SXin LI 			}
329208987a5SXin LI 			*orig = c;
330208987a5SXin LI 			--cp;
331208987a5SXin LI 			continue;
332208987a5SXin LI 		case 'x':	/* hexidecimal number */
333208987a5SXin LI 			cp++;	/* skip 'x' */
334208987a5SXin LI 			for (i = 0, c = 0;
335208987a5SXin LI 			     isxdigit((unsigned char)*cp) && i < 2;
336208987a5SXin LI 			     i++, cp++) {
337208987a5SXin LI 				c <<= 4;
338208987a5SXin LI 				if (isdigit((unsigned char)*cp))
339208987a5SXin LI 					c |= (*cp - '0');
340208987a5SXin LI 				else
341208987a5SXin LI 					c |= ((toupper((unsigned char)*cp) -
342208987a5SXin LI 					    'A') + 10);
343208987a5SXin LI 			}
344208987a5SXin LI 			*orig = c;
345208987a5SXin LI 			--cp;
346208987a5SXin LI 			continue;
347208987a5SXin LI 		default:
348208987a5SXin LI 			--cp;
349208987a5SXin LI 			break;
350208987a5SXin LI 		}
351208987a5SXin LI 	}
352208987a5SXin LI 
353208987a5SXin LI 	return (new);
354208987a5SXin LI }
355208987a5SXin LI 
356208987a5SXin LI /*
357208987a5SXin LI  * e_atof - convert an ASCII string to a double
358208987a5SXin LI  *	exit if string is not a valid double, or if converted value would
359208987a5SXin LI  *	cause overflow or underflow
360208987a5SXin LI  */
361208987a5SXin LI double
362208987a5SXin LI e_atof(const char *num)
363208987a5SXin LI {
364208987a5SXin LI 	char *endp;
365208987a5SXin LI 	double dbl;
366208987a5SXin LI 
367208987a5SXin LI 	errno = 0;
368208987a5SXin LI 	dbl = strtod(num, &endp);
369208987a5SXin LI 
370208987a5SXin LI 	if (errno == ERANGE)
371208987a5SXin LI 		/* under or overflow */
372208987a5SXin LI 		err(2, "%s", num);
373208987a5SXin LI 	else if (*endp != '\0')
374208987a5SXin LI 		/* "junk" left in number */
375208987a5SXin LI 		errx(2, "invalid floating point argument: %s", num);
376208987a5SXin LI 
377208987a5SXin LI 	/* zero shall have no sign */
378208987a5SXin LI 	if (dbl == -0.0)
379208987a5SXin LI 		dbl = 0;
380208987a5SXin LI 	return (dbl);
381208987a5SXin LI }
382208987a5SXin LI 
383208987a5SXin LI /*
384208987a5SXin LI  * decimal_places - count decimal places in a number (string)
385208987a5SXin LI  */
386208987a5SXin LI int
387208987a5SXin LI decimal_places(const char *number)
388208987a5SXin LI {
389208987a5SXin LI 	int places = 0;
390208987a5SXin LI 	char *dp;
391208987a5SXin LI 
392208987a5SXin LI 	/* look for a decimal point */
393208987a5SXin LI 	if ((dp = strstr(number, decimal_point))) {
394208987a5SXin LI 		dp += strlen(decimal_point);
395208987a5SXin LI 
396208987a5SXin LI 		while (isdigit((unsigned char)*dp++))
397208987a5SXin LI 			places++;
398208987a5SXin LI 	}
399208987a5SXin LI 	return (places);
400208987a5SXin LI }
401208987a5SXin LI 
402208987a5SXin LI /*
403208987a5SXin LI  * generate_format - create a format string
404208987a5SXin LI  *
405208987a5SXin LI  * XXX to be bug for bug compatable with Plan9 and GNU return "%g"
406208987a5SXin LI  * when "%g" prints as "%e" (this way no width adjustments are made)
407208987a5SXin LI  */
408208987a5SXin LI char *
409208987a5SXin LI generate_format(double first, double incr, double last, int equalize, char pad)
410208987a5SXin LI {
411208987a5SXin LI 	static char buf[256];
412208987a5SXin LI 	char cc = '\0';
413208987a5SXin LI 	int precision, width1, width2, places;
414208987a5SXin LI 
415208987a5SXin LI 	if (equalize == 0)
416208987a5SXin LI 		return (default_format);
417208987a5SXin LI 
418208987a5SXin LI 	/* figure out "last" value printed */
419208987a5SXin LI 	if (first > last)
420208987a5SXin LI 		last = first - incr * floor((first - last) / incr);
421208987a5SXin LI 	else
422208987a5SXin LI 		last = first + incr * floor((last - first) / incr);
423208987a5SXin LI 
424208987a5SXin LI 	sprintf(buf, "%g", incr);
425208987a5SXin LI 	if (strchr(buf, 'e'))
426208987a5SXin LI 		cc = 'e';
427208987a5SXin LI 	precision = decimal_places(buf);
428208987a5SXin LI 
429208987a5SXin LI 	width1 = sprintf(buf, "%g", first);
430208987a5SXin LI 	if (strchr(buf, 'e'))
431208987a5SXin LI 		cc = 'e';
432208987a5SXin LI 	if ((places = decimal_places(buf)))
433208987a5SXin LI 		width1 -= (places + strlen(decimal_point));
434208987a5SXin LI 
435208987a5SXin LI 	precision = MAX(places, precision);
436208987a5SXin LI 
437208987a5SXin LI 	width2 = sprintf(buf, "%g", last);
438208987a5SXin LI 	if (strchr(buf, 'e'))
439208987a5SXin LI 		cc = 'e';
440208987a5SXin LI 	if ((places = decimal_places(buf)))
441208987a5SXin LI 		width2 -= (places + strlen(decimal_point));
442208987a5SXin LI 
443208987a5SXin LI 	if (precision) {
444208987a5SXin LI 		sprintf(buf, "%%%c%d.%d%c", pad,
445208987a5SXin LI 		    MAX(width1, width2) + (int) strlen(decimal_point) +
446208987a5SXin LI 		    precision, precision, (cc) ? cc : 'f');
447208987a5SXin LI 	} else {
448208987a5SXin LI 		sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2),
449208987a5SXin LI 		    (cc) ? cc : 'g');
450208987a5SXin LI 	}
451208987a5SXin LI 
452208987a5SXin LI 	return (buf);
453208987a5SXin LI }
454