diff options
Diffstat (limited to 'src/seq.c')
-rw-r--r-- | src/seq.c | 197 |
1 files changed, 189 insertions, 8 deletions
@@ -1,5 +1,5 @@ /* seq - print sequence of numbers to standard output. - Copyright (C) 1994-2012 Free Software Foundation, Inc. + Copyright (C) 1994-2013 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 @@ -72,7 +72,11 @@ Usage: %s [OPTION]... LAST\n\ "), program_name, program_name, program_name); fputs (_("\ Print numbers from FIRST to LAST, in steps of INCREMENT.\n\ -\n\ +"), stdout); + + emit_mandatory_arg_note (); + + fputs (_("\ -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\ @@ -166,6 +170,21 @@ scan_arg (const char *arg) { long exponent = strtol (e + 1, NULL, 10); ret.precision += exponent < 0 ? -exponent : 0; + /* Don't account for e.... in the width since this is not output. */ + ret.width -= strlen (arg) - (e - arg); + /* Adjust the width as per the exponent. */ + if (exponent < 0) + { + if (decimal_point) + { + if (e == decimal_point + 1) /* undo #. -> # above */ + ret.width++; + } + else + ret.width++; + exponent = -exponent; + } + ret.width += exponent; } } @@ -317,6 +336,8 @@ get_default_format (operand first, operand step, operand last) last_width--; /* don't include space for '.' */ if (last.precision == 0 && prec) last_width++; /* include space for '.' */ + if (first.precision == 0 && prec) + first_width++; /* include space for '.' */ size_t width = MAX (first_width, last_width); if (width <= INT_MAX) { @@ -335,6 +356,122 @@ get_default_format (operand first, operand step, operand last) return "%Lg"; } +/* The NUL-terminated string S0 of length S_LEN represents a valid + non-negative decimal integer. Adjust the string and length so + that the pair describe the next-larger value. */ +static void +incr (char **s0, size_t *s_len) +{ + char *s = *s0; + char *endp = s + *s_len - 1; + + do + { + if ((*endp)++ < '9') + return; + *endp-- = '0'; + } + while (endp >= s); + *--(*s0) = '1'; + ++*s_len; +} + +/* Compare A and B (each a NUL-terminated digit string), with lengths + given by A_LEN and B_LEN. Return +1 if A < B, -1 if B < A, else 0. */ +static int +cmp (char const *a, size_t a_len, char const *b, size_t b_len) +{ + if (a_len < b_len) + return -1; + if (b_len < a_len) + return 1; + return (strcmp (a, b)); +} + +/* Trim leading 0's from S, but if S is all 0's, leave one. + Return a pointer to the trimmed string. */ +static char const * _GL_ATTRIBUTE_PURE +trim_leading_zeros (char const *s) +{ + char const *p = s; + while (*s == '0') + ++s; + + /* If there were only 0's, back up, to leave one. */ + if (!*s && s != p) + --s; + return s; +} + +/* Print all whole numbers from A to B, inclusive -- to stdout, each + followed by a newline. If B < A, return false and print nothing. + Otherwise, return true. */ +static bool +seq_fast (char const *a, char const *b) +{ + /* Skip past any leading 0's. Without this, our naive cmp + function would declare 000 to be larger than 99. */ + a = trim_leading_zeros (a); + b = trim_leading_zeros (b); + + size_t p_len = strlen (a); + size_t q_len = strlen (b); + size_t n = MAX (p_len, q_len); + char *p0 = xmalloc (n + 1); + char *p = memcpy (p0 + n - p_len, a, p_len + 1); + char *q0 = xmalloc (n + 1); + char *q = memcpy (q0 + n - q_len, b, q_len + 1); + + bool ok = cmp (p, p_len, q, q_len) <= 0; + if (ok) + { + /* Buffer at least this many numbers per fwrite call. + This gives a speed-up of more than 2x over the unbuffered code + when printing the first 10^9 integers. */ + enum {N = 40}; + char *buf = xmalloc (N * (n + 1)); + char const *buf_end = buf + N * (n + 1); + + char *z = buf; + + /* Write first number to buffer. */ + z = mempcpy (z, p, p_len); + + /* Append separator then number. */ + while (cmp (p, p_len, q, q_len) < 0) + { + *z++ = *separator; + incr (&p, &p_len); + z = mempcpy (z, p, p_len); + /* If no place for another separator + number then + output buffer so far, and reset to start of buffer. */ + if (buf_end - (n + 1) < z) + { + fwrite (buf, z - buf, 1, stdout); + z = buf; + } + } + + /* Write any remaining buffered output, and the terminator. */ + *z++ = *terminator; + fwrite (buf, z - buf, 1, stdout); + + IF_LINT (free (buf)); + } + + free (p0); + free (q0); + return ok; +} + +/* Return true if S consists of at least one digit and no non-digits. */ +static bool _GL_ATTRIBUTE_PURE +all_digits_p (char const *s) +{ + size_t n = strlen (s); + return ISDIGIT (s[0]) && n == strspn (s, "0123456789"); +} + int main (int argc, char **argv) { @@ -397,13 +534,14 @@ main (int argc, char **argv) } } - if (argc - optind < 1) + unsigned int n_args = argc - optind; + if (n_args < 1) { error (0, 0, _("missing operand")); usage (EXIT_FAILURE); } - if (3 < argc - optind) + if (3 < n_args) { error (0, 0, _("extra operand %s"), quote (argv[optind + 3])); usage (EXIT_FAILURE); @@ -412,6 +550,33 @@ main (int argc, char **argv) if (format_str) format_str = long_double_format (format_str, &layout); + if (format_str != NULL && equal_width) + { + error (0, 0, _("format string may not be specified" + " when printing equal width strings")); + usage (EXIT_FAILURE); + } + + /* If the following hold: + - no format string, [FIXME: relax this, eventually] + - integer start (or no start) + - integer end + - increment == 1 or not specified [FIXME: relax this, eventually] + then use the much more efficient integer-only code. */ + if (all_digits_p (argv[optind]) + && (n_args == 1 || all_digits_p (argv[optind + 1])) + && (n_args < 3 || (STREQ ("1", argv[optind + 1]) + && all_digits_p (argv[optind + 2]))) + && !equal_width && !format_str && strlen (separator) == 1) + { + char const *s1 = n_args == 1 ? "1" : argv[optind]; + char const *s2 = argv[optind + (n_args - 1)]; + if (seq_fast (s1, s2)) + exit (EXIT_SUCCESS); + + /* Upon any failure, let the more general code deal with it. */ + } + last = scan_arg (argv[optind++]); if (optind < argc) @@ -426,11 +591,27 @@ main (int argc, char **argv) } } - if (format_str != NULL && equal_width) + if (first.precision == 0 && step.precision == 0 && last.precision == 0 + && 0 <= first.value && step.value == 1 && 0 <= last.value + && !equal_width && !format_str && strlen (separator) == 1) { - error (0, 0, _("format string may not be specified" - " when printing equal width strings")); - usage (EXIT_FAILURE); + char *s1; + char *s2; + if (asprintf (&s1, "%0.Lf", first.value) < 0) + xalloc_die (); + if (asprintf (&s2, "%0.Lf", last.value) < 0) + xalloc_die (); + + if (seq_fast (s1, s2)) + { + IF_LINT (free (s1)); + IF_LINT (free (s2)); + exit (EXIT_SUCCESS); + } + + free (s1); + free (s2); + /* Upon any failure, let the more general code deal with it. */ } if (format_str == NULL) |