summaryrefslogtreecommitdiffstats
path: root/examples/loadables/seq.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/loadables/seq.c')
-rw-r--r--examples/loadables/seq.c490
1 files changed, 490 insertions, 0 deletions
diff --git a/examples/loadables/seq.c b/examples/loadables/seq.c
new file mode 100644
index 0000000..e562407
--- /dev/null
+++ b/examples/loadables/seq.c
@@ -0,0 +1,490 @@
+/* seq - print sequence of numbers to standard output.
+ Copyright (C) 2018-2020 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* Written as bash builtin by Chet Ramey. Portions from seq.c by Ulrich Drepper. */
+
+#include <config.h>
+
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "bashansi.h"
+#include "loadables.h"
+#include "bashintl.h"
+
+#ifndef errno
+extern int errno;
+#endif
+
+#if defined (HAVE_LONG_DOUBLE) && HAVE_DECL_STRTOLD && !defined(STRTOLD_BROKEN)
+typedef long double floatmax_t;
+# define FLOATMAX_CONV "L"
+# define strtofltmax strtold
+# define FLOATMAX_FMT "%Lg"
+# define FLOATMAX_WFMT "%0.Lf"
+# define USE_LONG_DOUBLE
+#else
+typedef double floatmax_t;
+# define FLOATMAX_CONV ""
+# define strtofltmax strtod
+# define FLOATMAX_FMT "%g"
+# define FLOATMAX_WFMT "%0.f"
+#endif
+static floatmax_t getfloatmax PARAMS((const char *));
+static char *genformat PARAMS((floatmax_t, floatmax_t, floatmax_t));
+
+#define MAX(a, b) (((a) < (b))? (b) : (a))
+
+static int conversion_error = 0;
+
+/* If true print all number with equal width. */
+static int equal_width;
+
+/* The string used to separate two numbers. */
+static char const *separator;
+
+/* The string output after all numbers have been output. */
+static char const terminator[] = "\n";
+
+static char decimal_point;
+
+/* Pretty much the same as the version in builtins/printf.def */
+static floatmax_t
+getfloatmax (arg)
+ const char *arg;
+{
+ floatmax_t ret;
+ char *ep;
+
+ errno = 0;
+ ret = strtofltmax (arg, &ep);
+
+ if (*ep)
+ {
+ sh_invalidnum ((char *)arg);
+ conversion_error = 1;
+ }
+ else if (errno == ERANGE)
+ {
+ builtin_error ("warning: %s: %s", arg, strerror(ERANGE));
+ conversion_error = 1;
+ }
+
+ if (ret == -0.0)
+ ret = 0.0;
+
+ return (ret);
+}
+
+/* If FORMAT is a valid printf format for a double argument, return
+ its long double equivalent, allocated from dynamic storage. This
+ was written by Ulrich Drepper, taken from coreutils:seq.c */
+static char *
+long_double_format (char const *fmt)
+{
+ size_t i;
+ size_t length_modifier_offset;
+ int has_L;
+
+ for (i = 0; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i += (fmt[i] == '%') + 1)
+ {
+ if (!fmt[i])
+ {
+ builtin_error ("format %s has no %% directive", fmt);
+ return 0;
+ }
+ }
+
+ i++;
+ i += strspn (fmt + i, "-+#0 '"); /* zero or more flags */
+ i += strspn (fmt + i, "0123456789"); /* optional minimum field width */
+ if (fmt[i] == '.') /* optional precision */
+ {
+ i++;
+ i += strspn (fmt + i, "0123456789");
+ }
+
+ length_modifier_offset = i; /* optional length modifier */
+ /* we could ignore an 'l' length modifier here */
+ has_L = (fmt[i] == 'L');
+ i += has_L;
+ switch (fmt[i])
+ {
+ case '\0':
+ builtin_error ("format %s ends in %%", fmt);
+ return 0;
+ case 'A':
+ case 'a':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ break;
+ default:
+ builtin_error ("format %s has unknown `%%%c' directive", fmt, fmt[i]);
+ return 0;
+ }
+ for (i++; ; i += (fmt[i] == '%') + 1)
+ if (fmt[i] == '%' && fmt[i + 1] != '%')
+ {
+ builtin_error ("format %s has too many %% directives", fmt);
+ return 0;
+ }
+ else if (fmt[i] == 0)
+ {
+ size_t format_size = i + 1;
+ char *ldfmt = xmalloc (format_size + 1);
+ memcpy (ldfmt, fmt, length_modifier_offset);
+#ifdef USE_LONG_DOUBLE
+ ldfmt[length_modifier_offset] = 'L';
+ strcpy (ldfmt + length_modifier_offset + 1,
+ fmt + length_modifier_offset + has_L);
+#else
+ strcpy (ldfmt + length_modifier_offset, fmt + length_modifier_offset);
+#endif
+ return ldfmt;
+ }
+}
+
+/* Return the number of digits following the decimal point in NUMBUF */
+static int
+getprec (numbuf)
+ const char *numbuf;
+{
+ int p;
+ char *dp;
+
+ if (dp = strchr (numbuf, decimal_point))
+ dp++; /* skip over decimal point */
+ for (p = 0; dp && *dp && ISDIGIT (*dp); dp++)
+ p++;
+ return p;
+}
+
+/* Return the default format given FIRST, INCR, and LAST. */
+static char *
+genformat (first, incr, last)
+ floatmax_t first, incr, last;
+{
+ static char buf[6 + 2 * INT_STRLEN_BOUND (int)];
+ int wfirst, wlast, width;
+ int iprec, fprec, lprec, prec;
+
+ if (equal_width == 0)
+ return (FLOATMAX_FMT);
+
+ /* OK, we have to figure out the largest number of decimal places. This is
+ a little more expensive than using the original strings. */
+ snprintf (buf, sizeof (buf), FLOATMAX_FMT, incr);
+ iprec = getprec (buf);
+
+ wfirst = snprintf (buf, sizeof (buf), FLOATMAX_FMT, first);
+ fprec = getprec (buf);
+
+ prec = MAX (fprec, iprec);
+
+ wlast = snprintf (buf, sizeof (buf), FLOATMAX_FMT, last);
+ lprec = getprec (buf);
+
+ /* increase first width by any increased precision in increment */
+ wfirst += (prec - fprec);
+
+ /* adjust last width to use precision from first/incr */
+ wlast += (prec - lprec);
+
+ if (lprec && prec == 0)
+ wlast--; /* no decimal point */
+ if (lprec == 0 && prec)
+ wlast++; /* include decimal point */
+ if (fprec == 0 && prec)
+ wfirst++; /* include decimal point */
+
+ width = MAX (wfirst, wlast);
+ if (width)
+ sprintf (buf, "%%0%d.%d%sf", width, prec, FLOATMAX_CONV);
+ else
+ sprintf (buf, "%%.%d%sf", prec, FLOATMAX_CONV);
+
+ return buf;
+}
+
+int
+print_fltseq (fmt, first, last, incr)
+ const char *fmt;
+ floatmax_t first, last, incr;
+{
+ int n;
+ floatmax_t next;
+ const char *s;
+
+ n = 0; /* iteration counter */
+ s = "";
+ for (next = first; incr >= 0 ? (next <= last) : (next >= last); next = first + n * incr)
+ {
+ QUIT;
+ if (*s && fputs (s, stdout) == EOF)
+ return (sh_chkwrite (EXECUTION_FAILURE));
+ if (printf (fmt, next) < 0)
+ return (sh_chkwrite (EXECUTION_FAILURE));
+ s = separator;
+ n++;
+ }
+
+ if (n > 0 && fputs (terminator, stdout) == EOF)
+ return (sh_chkwrite (EXECUTION_FAILURE));
+ return (sh_chkwrite (EXECUTION_SUCCESS));
+}
+
+/* must be <= INT_STRLEN_BOUND(intmax_t) */
+int
+width_needed (num)
+ intmax_t num;
+{
+ int ret;
+
+ ret = num < 0; /* sign */
+ if (ret)
+ num = -num;
+ do
+ ret++;
+ while (num /= 10);
+ return ret;
+}
+
+int
+print_intseq (ifirst, ilast, iincr)
+ intmax_t ifirst, ilast, iincr;
+{
+ char intwfmt[6 + INT_STRLEN_BOUND(int) + sizeof (PRIdMAX)];
+ const char *s;
+ intmax_t i, next;
+
+ /* compute integer format string */
+ if (equal_width) /* -w supplied */
+ {
+ int wfirst, wlast, width;
+
+ wfirst = width_needed (ifirst);
+ wlast = width_needed (ilast);
+ width = MAX(wfirst, wlast);
+
+ /* The leading %s is for the separator */
+ snprintf (intwfmt, sizeof (intwfmt), "%%s%%0%u" PRIdMAX, width);
+ }
+
+ /* We could use braces.c:mkseq here but that allocates lots of memory */
+ s = "";
+ for (i = ifirst; (ifirst <= ilast) ? (i <= ilast) : (i >= ilast); i = next)
+ {
+ QUIT;
+ /* The leading %s is for the separator */
+ if (printf (equal_width ? intwfmt : "%s%" PRIdMAX, s, i) < 0)
+ return (sh_chkwrite (EXECUTION_FAILURE));
+ s = separator;
+ next = i + iincr;
+ }
+
+ if (fputs (terminator, stdout) == EOF)
+ return (sh_chkwrite (EXECUTION_FAILURE));
+ return (sh_chkwrite (EXECUTION_SUCCESS));
+}
+
+int
+seq_builtin (list)
+ WORD_LIST *list;
+{
+ floatmax_t first, last, incr;
+ intmax_t ifirst, ilast, iincr;
+ WORD_LIST *l;
+ int opt, nargs, intseq, freefmt;
+ char *first_str, *incr_str, *last_str;
+ char const *fmtstr; /* The printf(3) format used for output. */
+
+ equal_width = 0;
+ separator = "\n";
+ fmtstr = NULL;
+
+ first = 1.0;
+ last = 0.0;
+ incr = 0.0; /* set later */
+ ifirst = ilast = iincr = 0;
+ first_str = incr_str = last_str = 0;
+
+ intseq = freefmt = 0;
+ opt = 0;
+
+ reset_internal_getopt ();
+ while (opt != -1)
+ {
+ l = lcurrent ? lcurrent : list;
+ if (l && l->word && l->word->word && l->word->word[0] == '-' &&
+ (l->word->word[1] == '.' || DIGIT (l->word->word[1])))
+ {
+ loptend = l;
+ break; /* negative number */
+ }
+ if ((opt = internal_getopt (list, "f:s:w")) == -1)
+ break;
+
+ switch (opt)
+ {
+ case 'f':
+ fmtstr = list_optarg;
+ break;
+ case 's':
+ separator = list_optarg;
+ break;
+ case 'w':
+ equal_width = 1;
+ break;
+ CASE_HELPOPT;
+ default:
+ builtin_usage ();
+ return (EX_USAGE);
+ }
+ }
+ list = loptend;
+
+ if (list == 0)
+ {
+ builtin_usage ();
+ return (EXECUTION_FAILURE);
+ }
+
+ for (nargs = 1, l = list; l->next; l = l->next)
+ nargs++;
+ if (nargs > 3)
+ {
+ builtin_usage ();
+ return (EXECUTION_FAILURE);
+ }
+
+ /* LAST */
+ conversion_error = 0;
+ last = getfloatmax (last_str = l->word->word);
+ if (conversion_error)
+ return (EXECUTION_FAILURE);
+
+ /* FIRST LAST */
+ if (nargs > 1)
+ {
+ conversion_error = 0;
+ first = getfloatmax (first_str = list->word->word);
+ if (conversion_error)
+ return (EXECUTION_FAILURE);
+ }
+
+ /* FIRST INCR LAST */
+ if (nargs > 2)
+ {
+ conversion_error = 0;
+ incr = getfloatmax (incr_str = list->next->word->word);
+ if (conversion_error)
+ return (EXECUTION_FAILURE);
+ if (incr == 0.0)
+ {
+ builtin_error ("zero %screment", (first < last) ? "in" : "de");
+ return (EXECUTION_FAILURE);
+ }
+ }
+
+ /* Sanitize arguments */
+ if (incr == 0.0)
+ incr = (first <= last) ? 1.0 : -1.0;
+ if ((incr < 0.0 && first < last) || (incr > 0 && first > last))
+ {
+ builtin_error ("incorrect %screment", (first < last) ? "in" : "de");
+ return (EXECUTION_FAILURE);
+ }
+
+ /* validate format here */
+ if (fmtstr)
+ {
+ fmtstr = long_double_format (fmtstr);
+ freefmt = 1;
+ if (fmtstr == 0)
+ return (EXECUTION_FAILURE);
+ }
+
+ if (fmtstr != NULL && equal_width)
+ {
+ builtin_warning ("-w ignored when the format string is specified");
+ equal_width = 0;
+ }
+
+ /* Placeholder for later additional conditions */
+ if (last_str && all_digits (last_str) &&
+ (first_str == 0 || all_digits (first_str)) &&
+ (incr_str == 0 || all_digits (incr_str)) &&
+ fmtstr == NULL)
+ intseq = 1;
+
+ if (intseq)
+ {
+ ifirst = (intmax_t)first; /* truncation */
+ ilast = (intmax_t)last;
+ iincr = (intmax_t)incr;
+
+ return (print_intseq (ifirst, ilast, iincr));
+ }
+
+ decimal_point = locale_decpoint ();
+ if (fmtstr == NULL)
+ fmtstr = genformat (first, incr, last);
+
+ print_fltseq (fmtstr, first, last, incr);
+
+ if (freefmt)
+ free ((void *)fmtstr);
+ return sh_chkwrite (EXECUTION_SUCCESS);
+}
+
+/* Taken largely from GNU seq. */
+char *seq_doc[] = {
+ "Print numbers from FIRST to LAST, in steps of INCREMENT.",
+ "",
+ "-f FORMAT use printf style floating-point FORMAT",
+ "-s STRING use STRING to separate numbers (default: \\n)",
+ "-w equalize width by padding with leading zeroes",
+ "",
+ "If FIRST or INCREMENT is omitted, it defaults to 1. However, an",
+ "omitted INCREMENT defaults to -1 when LAST is smaller than FIRST.",
+ "The sequence of numbers ends when the sum of the current number and",
+ "INCREMENT would become greater than LAST.",
+ "FIRST, INCREMENT, and LAST are interpreted as floating point values.",
+ "",
+ "FORMAT must be suitable for printing one argument of type 'double';",
+ "it defaults to %.PRECf if FIRST, INCREMENT, and LAST are all fixed point",
+ "decimal numbers with maximum precision PREC, and to %g otherwise.",
+ (char *)NULL
+};
+
+struct builtin seq_struct = {
+ "seq",
+ seq_builtin,
+ BUILTIN_ENABLED,
+ seq_doc,
+ "seq [-f format] [-s separator] [-w] [FIRST [INCR]] LAST",
+ 0
+};