]> git.ipfire.org Git - thirdparty/bash.git/blame - examples/loadables/seq.c
commit bash-20181116 snapshot
[thirdparty/bash.git] / examples / loadables / seq.c
CommitLineData
93e3d9f6
CR
1/* seq - print sequence of numbers to standard output.
2 Copyright (C) 2018 Free Software Foundation, Inc.
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
16
17/* Written as bash builtin by Chet Ramey. Portions from seq.c by Ulrich Drepper. */
18
19#include <config.h>
20
21#include <sys/types.h>
22
23#ifdef HAVE_UNISTD_H
24# include <unistd.h>
25#endif
26
27#include <stdio.h>
28#include <errno.h>
29
30#include "bashansi.h"
31#include "loadables.h"
32#include "bashintl.h"
33
34#ifndef errno
35extern int errno;
36#endif
37
38#if defined (HAVE_LONG_DOUBLE) && HAVE_DECL_STRTOLD && !defined(STRTOLD_BROKEN)
39typedef long double floatmax_t;
40# define FLOATMAX_CONV "L"
41# define strtofltmax strtold
42# define FLOATMAX_FMT "%Lg"
43# define FLOATMAX_WFMT "%0.Lf"
44# define USE_LONG_DOUBLE
45#else
46typedef double floatmax_t;
47# define FLOATMAX_CONV ""
48# define strtofltmax strtod
49# define FLOATMAX_FMT "%g"
50# define FLOATMAX_WFMT "%0.f"
51#endif
52static floatmax_t getfloatmax __P((const char *));
53static char *genformat __P((floatmax_t, floatmax_t, floatmax_t));
54
55#define MAX(a, b) (((a) < (b))? (b) : (a))
56
57static int conversion_error = 0;
58
59/* If true print all number with equal width. */
60static int equal_width;
61
62/* The string used to separate two numbers. */
63static char const *separator;
64
65/* The string output after all numbers have been output. */
66static char const terminator[] = "\n";
67
68static char decimal_point;
69
70/* Pretty much the same as the version in builtins/printf.def */
71static floatmax_t
72getfloatmax (arg)
73 const char *arg;
74{
75 floatmax_t ret;
76 char *ep;
77
78 errno = 0;
79 ret = strtofltmax (arg, &ep);
80
81 if (*ep)
82 {
83 sh_invalidnum ((char *)arg);
84 conversion_error = 1;
85 }
86 else if (errno == ERANGE)
87 {
88 builtin_error ("warning: %s: %s", arg, strerror(ERANGE));
89 conversion_error = 1;
90 }
91
92 if (ret == -0.0)
93 ret = 0.0;
94
95 return (ret);
96}
97
98/* If FORMAT is a valid printf format for a double argument, return
99 its long double equivalent, allocated from dynamic storage. This
100 was written by Ulrich Drepper, taken from coreutils:seq.c */
101static char *
102long_double_format (char const *fmt)
103{
104 size_t i;
105 size_t length_modifier_offset;
106 int has_L;
107
108 for (i = 0; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i += (fmt[i] == '%') + 1)
109 {
110 if (!fmt[i])
111 {
112 builtin_error ("format %s has no %% directive", fmt);
113 return 0;
114 }
115 }
116
117 i++;
118 i += strspn (fmt + i, "-+#0 '"); /* zero or more flags */
119 i += strspn (fmt + i, "0123456789"); /* optional minimum field width */
120 if (fmt[i] == '.') /* optional precision */
121 {
122 i++;
123 i += strspn (fmt + i, "0123456789");
124 }
125
126 length_modifier_offset = i; /* optional length modifier */
127 /* we could ignore an 'l' length modifier here */
128 has_L = (fmt[i] == 'L');
129 i += has_L;
130 switch (fmt[i])
131 {
132 case '\0':
133 builtin_error ("format %s ends in %%", fmt);
134 return 0;
135 case 'A':
136 case 'a':
137 case 'e':
138 case 'E':
139 case 'f':
140 case 'F':
141 case 'g':
142 case 'G':
143 break;
144 default:
145 builtin_error ("format %s has unknown `%%%c' directive", fmt, fmt[i]);
146 return 0;
147 }
148 for (i++; ; i += (fmt[i] == '%') + 1)
149 if (fmt[i] == '%' && fmt[i + 1] != '%')
150 {
151 builtin_error ("format %s has too many %% directives", fmt);
152 return 0;
153 }
154 else if (fmt[i] == 0)
155 {
156 size_t format_size = i + 1;
157 char *ldfmt = xmalloc (format_size + 1);
158 memcpy (ldfmt, fmt, length_modifier_offset);
159#ifdef USE_LONG_DOUBLE
160 ldfmt[length_modifier_offset] = 'L';
161 strcpy (ldfmt + length_modifier_offset + 1,
162 fmt + length_modifier_offset + has_L);
163#else
164 strcpy (ldfmt + length_modifier_offset, fmt + length_modifier_offset)
165#endif
166 return ldfmt;
167 }
168}
169
170/* Return the number of digits following the decimal point in NUMBUF */
171static int
172getprec (numbuf)
173 const char *numbuf;
174{
175 int p;
176 char *dp;
177
178 if (dp = strchr (numbuf, decimal_point))
179 dp++; /* skip over decimal point */
180 for (p = 0; dp && *dp && ISDIGIT (*dp); dp++)
181 p++;
182 return p;
183}
184
185/* Return the default format given FIRST, INCR, and LAST. */
186static char *
187genformat (first, incr, last)
188 floatmax_t first, incr, last;
189{
190 static char buf[6 + 2 * INT_STRLEN_BOUND (int)];
191 int wfirst, wlast, width;
192 int iprec, fprec, lprec, prec;
193
194 if (equal_width == 0)
195 return (FLOATMAX_FMT);
196
197 /* OK, we have to figure out the largest number of decimal places. This is
198 a little more expensive than using the original strings. */
199 snprintf (buf, sizeof (buf), FLOATMAX_FMT, incr);
200 iprec = getprec (buf);
201
202 wfirst = snprintf (buf, sizeof (buf), FLOATMAX_FMT, first);
203 fprec = getprec (buf);
204
205 prec = MAX (fprec, iprec);
206
207 wlast = snprintf (buf, sizeof (buf), FLOATMAX_FMT, last);
208 lprec = getprec (buf);
209
210 /* increase first width by any increased precision in increment */
211 wfirst += (prec - fprec);
212
213 /* adjust last width to use precision from first/incr */
214 wlast += (prec - lprec);
215
216 if (lprec && prec == 0)
217 wlast--; /* no decimal point */
218 if (lprec == 0 && prec)
219 wlast++; /* include decimal point */
220 if (fprec == 0 && prec)
221 wfirst++; /* include decimal point */
222
223 width = MAX (wfirst, wlast);
224 if (width)
225 sprintf (buf, "%%0%d.%d%sf", width, prec, FLOATMAX_CONV);
226 else
227 sprintf (buf, "%%.%d%sf", prec, FLOATMAX_CONV);
228
229 return buf;
230}
231
232int
233print_fltseq (fmt, first, last, incr)
234 const char *fmt;
235 floatmax_t first, last, incr;
236{
237 int n;
238 floatmax_t next;
239 const char *s;
240
241 n = 0; /* interation counter */
242 s = "";
243 for (next = first; incr >= 0 ? (next <= last) : (next >= last); next = first + n * incr)
244 {
245 QUIT;
246 if (*s && fputs (s, stdout) == EOF)
247 return (sh_chkwrite (EXECUTION_FAILURE));
248 if (printf (fmt, next) < 0)
249 return (sh_chkwrite (EXECUTION_FAILURE));
250 s = separator;
251 n++;
252 }
253
254 if (n > 0 && fputs (terminator, stdout) == EOF)
255 return (sh_chkwrite (EXECUTION_FAILURE));
256 return (sh_chkwrite (EXECUTION_SUCCESS));
257}
258
259/* must be <= INT_STRLEN_BOUND(intmax_t) */
260int
261width_needed (num)
262 intmax_t num;
263{
264 int ret;
265
266 ret = num < 0; /* sign */
267 if (ret)
268 num = -num;
269 do
270 ret++;
271 while (num /= 10);
272 return ret;
273}
274
275int
276print_intseq (ifirst, ilast, iincr)
277 intmax_t ifirst, ilast, iincr;
278{
279 char intwfmt[6 + INT_STRLEN_BOUND(int) + sizeof (PRIdMAX)];
280 const char *s;
281 intmax_t i, next;
282
283 /* compute integer format string */
284 if (equal_width) /* -w supplied */
285 {
286 int wfirst, wlast, width;
287
288 wfirst = width_needed (ifirst);
289 wlast = width_needed (ilast);
290 width = MAX(wfirst, wlast);
291
292 /* The leading %s is for the separator */
293 snprintf (intwfmt, sizeof (intwfmt), "%%s%%0%u" PRIdMAX, width);
294 }
295
296 /* We could use braces.c:mkseq here but that allocates lots of memory */
297 s = "";
298 for (i = ifirst; (ifirst <= ilast) ? (i <= ilast) : (i >= ilast); i = next)
299 {
300 QUIT;
301 /* The leading %s is for the separator */
302 if (printf (equal_width ? intwfmt : "%s%" PRIdMAX, s, i) < 0)
303 return (sh_chkwrite (EXECUTION_FAILURE));
304 s = separator;
305 next = i + iincr;
306 }
307
308 if (fputs (terminator, stdout) == EOF)
309 return (sh_chkwrite (EXECUTION_FAILURE));
310 return (sh_chkwrite (EXECUTION_SUCCESS));
311}
312
313int
314seq_builtin (list)
315 WORD_LIST *list;
316{
317 floatmax_t first, last, incr;
318 intmax_t ifirst, ilast, iincr;
319 WORD_LIST *l;
320 int opt, nargs, intseq, freefmt;
321 char *first_str, *incr_str, *last_str;
322 char const *fmtstr; /* The printf(3) format used for output. */
323
324 equal_width = 0;
325 separator = "\n";
326 fmtstr = NULL;
327
328 first = 1.0;
329 last = 0.0;
330 incr = 0.0; /* set later */
331 ifirst = ilast = iincr = 0;
332 first_str = incr_str = last_str = 0;
333
334 intseq = freefmt = 0;
335 opt = 0;
336
337 reset_internal_getopt ();
338 while (opt != -1)
339 {
340 l = lcurrent ? lcurrent : list;
341 if (l && l->word && l->word->word && l->word->word[0] == '-' &&
342 (l->word->word[1] == '.' || DIGIT (l->word->word[1])))
343 {
344 loptend = l;
345 break; /* negative number */
346 }
347 if ((opt = internal_getopt (list, "f:s:w")) == -1)
348 break;
349
350 switch (opt)
351 {
352 case 'f':
353 fmtstr = list_optarg;
354 break;
355 case 's':
356 separator = list_optarg;
357 break;
358 case 'w':
359 equal_width = 1;
360 break;
361 CASE_HELPOPT;
362 default:
363 builtin_usage ();
364 return (EX_USAGE);
365 }
366 }
367 list = loptend;
368
369 if (list == 0)
370 {
371 builtin_usage ();
372 return (EXECUTION_FAILURE);
373 }
374
375 for (nargs = 1, l = list; l->next; l = l->next)
376 nargs++;
377 if (nargs > 3)
378 {
379 builtin_usage ();
380 return (EXECUTION_FAILURE);
381 }
382
383 /* LAST */
384 conversion_error = 0;
385 last = getfloatmax (last_str = l->word->word);
386 if (conversion_error)
387 return (EXECUTION_FAILURE);
388
389 /* FIRST LAST */
390 if (nargs > 1)
391 {
392 conversion_error = 0;
393 first = getfloatmax (first_str = list->word->word);
394 if (conversion_error)
395 return (EXECUTION_FAILURE);
396 }
397
398 /* FIRST INCR LAST */
399 if (nargs > 2)
400 {
401 conversion_error = 0;
402 incr = getfloatmax (incr_str = list->next->word->word);
403 if (conversion_error)
404 return (EXECUTION_FAILURE);
405 if (incr == 0.0)
406 {
407 builtin_error ("zero %screment", (first < last) ? "in" : "de");
408 return (EXECUTION_FAILURE);
409 }
410 }
411
412 /* Sanitize arguments */
413 if (incr == 0.0)
414 incr = (first <= last) ? 1.0 : -1.0;
415 if ((incr < 0.0 && first < last) || (incr > 0 && first > last))
416 {
417 builtin_error ("incorrect %screment", (first < last) ? "in" : "de");
418 return (EXECUTION_FAILURE);
419 }
420
421 /* validate format here */
422 if (fmtstr)
423 {
424 fmtstr = long_double_format (fmtstr);
425 freefmt = 1;
426 if (fmtstr == 0)
427 return (EXECUTION_FAILURE);
428 }
429
430 if (fmtstr != NULL && equal_width)
431 {
432 builtin_warning ("-w ignored when the format string is specified");
433 equal_width = 0;
434 }
435
436 /* Placeholder for later additional conditions */
437 if (last_str && all_digits (last_str) &&
438 (first_str == 0 || all_digits (first_str)) &&
439 (incr_str == 0 || all_digits (incr_str)) &&
440 fmtstr == NULL)
441 intseq = 1;
442
443 if (intseq)
444 {
445 ifirst = (intmax_t)first; /* truncation */
446 ilast = (intmax_t)last;
447 iincr = (intmax_t)incr;
448
449 return (print_intseq (ifirst, ilast, iincr));
450 }
451
452 decimal_point = locale_decpoint ();
453 if (fmtstr == NULL)
454 fmtstr = genformat (first, incr, last);
455
456 print_fltseq (fmtstr, first, last, incr);
457
458 if (freefmt)
459 free ((void *)fmtstr);
460 return sh_chkwrite (EXECUTION_SUCCESS);
461}
462
463/* Taken largely from GNU seq. */
464char *seq_doc[] = {
465 "Print numbers from FIRST to LAST, in steps of INCREMENT.",
466 "",
467 "-f FORMAT use printf style floating-point FORMAT",
468 "-s STRING use STRING to separate numbers (default: \\n)",
469 "-w equalize width by padding with leading zeroes",
470 "",
471 "If FIRST or INCREMENT is omitted, it defaults to 1. However, an",
472 "omitted INCREMENT defaults to -1 when LAST is smaller than FIRST.",
473 "The sequence of numbers ends when the sum of the current number and",
474 "INCREMENT would become greater than LAST.",
475 "FIRST, INCREMENT, and LAST are interpreted as floating point values.",
476 "",
477 "FORMAT must be suitable for printing one argument of type 'double';",
478 "it defaults to %.PRECf if FIRST, INCREMENT, and LAST are all fixed point",
479 "decimal numbers with maximum precision PREC, and to %g otherwise.",
480 (char *)NULL
481};
482
483struct builtin seq_struct = {
484 "seq",
485 seq_builtin,
486 BUILTIN_ENABLED,
487 seq_doc,
488 "seq [-f format] [-s separator] [-w] [FIRST [INCR]] LAST",
489 0
490};