summaryrefslogtreecommitdiff
path: root/src/seq.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/seq.c')
-rw-r--r--src/seq.c375
1 files changed, 375 insertions, 0 deletions
diff --git a/src/seq.c b/src/seq.c
new file mode 100644
index 0000000..59dd318
--- /dev/null
+++ b/src/seq.c
@@ -0,0 +1,375 @@
+/* seq - print sequence of numbers to standard output.
+ Copyright (C) 1994-2006 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 2, 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, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+/* Written by Ulrich Drepper. */
+
+#include <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "system.h"
+#include "c-strtod.h"
+#include "error.h"
+#include "quote.h"
+#include "xstrtod.h"
+
+/* Roll our own isfinite rather than using <math.h>, so that we don't
+ have to worry about linking -lm just for isfinite. */
+#ifndef isfinite
+# define isfinite(x) ((x) * 0 == 0)
+#endif
+
+/* The official name of this program (e.g., no `g' prefix). */
+#define PROGRAM_NAME "seq"
+
+#define AUTHORS "Ulrich Drepper"
+
+/* If true print all number with equal width. */
+static bool equal_width;
+
+/* The name that this program was run with. */
+char *program_name;
+
+/* The string used to separate two numbers. */
+static char const *separator;
+
+/* The string output after all numbers have been output.
+ Usually "\n" or "\0". */
+/* FIXME: make this an option. */
+static char const terminator[] = "\n";
+
+static struct option const long_options[] =
+{
+ { "equal-width", no_argument, NULL, 'w'},
+ { "format", required_argument, NULL, 'f'},
+ { "separator", required_argument, NULL, 's'},
+ {GETOPT_HELP_OPTION_DECL},
+ {GETOPT_VERSION_OPTION_DECL},
+ { NULL, 0, NULL, 0}
+};
+
+void
+usage (int status)
+{
+ if (status != EXIT_SUCCESS)
+ fprintf (stderr, _("Try `%s --help' for more information.\n"),
+ program_name);
+ else
+ {
+ printf (_("\
+Usage: %s [OPTION]... LAST\n\
+ or: %s [OPTION]... FIRST LAST\n\
+ or: %s [OPTION]... FIRST INCREMENT LAST\n\
+"), program_name, program_name, program_name);
+ fputs (_("\
+Print numbers from FIRST to LAST, in steps of INCREMENT.\n\
+\n\
+ -f, --format=FORMAT use printf style floating-point FORMAT\n\
+ -s, --separator=STRING use STRING to separate numbers (default: \\n)\n\
+ -w, --equal-width equalize width by padding with leading zeroes\n\
+"), stdout);
+ fputs (HELP_OPTION_DESCRIPTION, stdout);
+ fputs (VERSION_OPTION_DESCRIPTION, stdout);
+ fputs (_("\
+\n\
+If FIRST or INCREMENT is omitted, it defaults to 1. That is, an\n\
+omitted INCREMENT defaults to 1 even when LAST is smaller than FIRST.\n\
+FIRST, INCREMENT, and LAST are interpreted as floating point values.\n\
+INCREMENT is usually positive if FIRST is smaller than LAST, and\n\
+INCREMENT is usually negative if FIRST is greater than LAST.\n\
+"), stdout);
+ fputs (_("\
+FORMAT must be suitable for printing one argument of type `double';\n\
+it defaults to %.PRECf if FIRST, INCREMENT, and LAST are all fixed point\n\
+decimal numbers with maximum precision PREC, and to %g otherwise.\n\
+"), stdout);
+ printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ }
+ exit (status);
+}
+
+/* A command-line operand. */
+struct operand
+{
+ /* Its value, converted to 'long double'. */
+ long double value;
+
+ /* Its print width, if it were printed out in a form similar to its
+ input form. An input like "-.1" is treated like "-0.1", and an
+ input like "1." is treated like "1", but otherwise widths are
+ left alone. */
+ size_t width;
+
+ /* Number of digits after the decimal point, or INT_MAX if the
+ number can't easily be expressed as a fixed-point number. */
+ int precision;
+};
+typedef struct operand operand;
+
+/* Read a long double value from the command line.
+ Return if the string is correct else signal error. */
+
+static operand
+scan_arg (const char *arg)
+{
+ operand ret;
+
+ if (! xstrtold (arg, NULL, &ret.value, c_strtold))
+ {
+ error (0, 0, _("invalid floating point argument: %s"), arg);
+ usage (EXIT_FAILURE);
+ }
+
+ ret.width = strlen (arg);
+ ret.precision = INT_MAX;
+
+ if (! arg[strcspn (arg, "eExX")] && isfinite (ret.value))
+ {
+ char const *decimal_point = strchr (arg, '.');
+ if (! decimal_point)
+ ret.precision = 0;
+ else
+ {
+ size_t fraction_len = strlen (decimal_point + 1);
+ if (fraction_len <= INT_MAX)
+ ret.precision = fraction_len;
+ ret.width += (fraction_len == 0
+ ? -1
+ : (decimal_point == arg
+ || ! ISDIGIT (decimal_point[-1])));
+ }
+ }
+
+ return ret;
+}
+
+/* If FORMAT is a valid printf format for a double argument, return
+ its long double equivalent, possibly allocated from dynamic
+ storage; otherwise, return NULL. */
+
+static char const *
+long_double_format (char const *fmt)
+{
+ size_t i;
+ size_t prefix_len;
+ bool has_L;
+
+ for (i = 0; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i++)
+ if (! fmt[i])
+ return NULL;
+
+ i++;
+ i += strspn (fmt + i, "-+#0 '");
+ i += strspn (fmt + i, "0123456789");
+ if (fmt[i] == '.')
+ {
+ i++;
+ i += strspn (fmt + i, "0123456789");
+ }
+
+ prefix_len = i;
+ has_L = (fmt[i] == 'L');
+ i += has_L;
+ if (! strchr ("efgaEFGA", fmt[i]))
+ return NULL;
+
+ for (i++; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i++)
+ if (! fmt[i])
+ {
+ size_t format_size = i + 1;
+ char *ldfmt = xmalloc (format_size + 1);
+ memcpy (ldfmt, fmt, prefix_len);
+ ldfmt[prefix_len] = 'L';
+ strcpy (ldfmt + prefix_len + 1, fmt + prefix_len + has_L);
+ return ldfmt;
+ }
+
+ return NULL;
+}
+
+/* Actually print the sequence of numbers in the specified range, with the
+ given or default stepping and format. */
+
+static void
+print_numbers (char const *fmt,
+ long double first, long double step, long double last)
+{
+ long double i;
+
+ for (i = 0; /* empty */; i++)
+ {
+ long double x = first + i * step;
+ if (step < 0 ? x < last : last < x)
+ break;
+ if (i)
+ fputs (separator, stdout);
+ printf (fmt, x);
+ }
+
+ if (i)
+ fputs (terminator, stdout);
+}
+
+/* Return the default format given FIRST, STEP, and LAST. */
+static char const *
+get_default_format (operand first, operand step, operand last)
+{
+ static char format_buf[sizeof "%0.Lf" + 2 * INT_STRLEN_BOUND (int)];
+
+ int prec = MAX (first.precision, step.precision);
+
+ if (prec != INT_MAX && last.precision != INT_MAX)
+ {
+ if (equal_width)
+ {
+ size_t first_width = first.width + (prec - first.precision);
+ size_t last_width = last.width + (prec - last.precision);
+ if (first.width <= first_width
+ && (last.width < last_width) == (prec < last.precision))
+ {
+ size_t width = MAX (first_width, last_width);
+ if (width <= INT_MAX)
+ {
+ int w = width;
+ sprintf (format_buf, "%%0%d.%dLf", w, prec);
+ return format_buf;
+ }
+ }
+ }
+ else
+ {
+ sprintf (format_buf, "%%.%dLf", prec);
+ return format_buf;
+ }
+ }
+
+ return "%Lg";
+}
+
+int
+main (int argc, char **argv)
+{
+ int optc;
+ operand first = { 1, 1, 0 };
+ operand step = { 1, 1, 0 };
+ operand last;
+
+ /* The printf(3) format used for output. */
+ char const *format_str = NULL;
+
+ initialize_main (&argc, &argv);
+ program_name = argv[0];
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ atexit (close_stdout);
+
+ equal_width = false;
+ separator = "\n";
+
+ /* We have to handle negative numbers in the command line but this
+ conflicts with the command line arguments. So explicitly check first
+ whether the next argument looks like a negative number. */
+ while (optind < argc)
+ {
+ if (argv[optind][0] == '-'
+ && ((optc = argv[optind][1]) == '.' || ISDIGIT (optc)))
+ {
+ /* means negative number */
+ break;
+ }
+
+ optc = getopt_long (argc, argv, "+f:s:w", long_options, NULL);
+ if (optc == -1)
+ break;
+
+ switch (optc)
+ {
+ case 'f':
+ format_str = optarg;
+ break;
+
+ case 's':
+ separator = optarg;
+ break;
+
+ case 'w':
+ equal_width = true;
+ break;
+
+ case_GETOPT_HELP_CHAR;
+
+ case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+
+ default:
+ usage (EXIT_FAILURE);
+ }
+ }
+
+ if (argc - optind < 1)
+ {
+ error (0, 0, _("missing operand"));
+ usage (EXIT_FAILURE);
+ }
+
+ if (3 < argc - optind)
+ {
+ error (0, 0, _("extra operand %s"), quote (argv[optind + 3]));
+ usage (EXIT_FAILURE);
+ }
+
+ if (format_str)
+ {
+ char const *f = long_double_format (format_str);
+ if (! f)
+ {
+ error (0, 0, _("invalid format string: %s"), quote (format_str));
+ usage (EXIT_FAILURE);
+ }
+ format_str = f;
+ }
+
+ last = scan_arg (argv[optind++]);
+
+ if (optind < argc)
+ {
+ first = last;
+ last = scan_arg (argv[optind++]);
+
+ if (optind < argc)
+ {
+ step = last;
+ last = scan_arg (argv[optind++]);
+ }
+ }
+
+ if (format_str != NULL && equal_width)
+ {
+ error (0, 0, _("\
+format string may not be specified when printing equal width strings"));
+ usage (EXIT_FAILURE);
+ }
+
+ if (format_str == NULL)
+ format_str = get_default_format (first, step, last);
+
+ print_numbers (format_str, first.value, step.value, last.value);
+
+ exit (EXIT_SUCCESS);
+}