aboutsummaryrefslogtreecommitdiff
path: root/toys/lsb/seq.c
blob: 7a931c64ca352808516317b0240d1874bf23f7af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/* seq.c - Count from first to last, by increment.
 *
 * Copyright 2006 Rob Landley <rob@landley.net>
 *
 * http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/seq.html

USE_SEQ(NEWTOY(seq, "<1>3?f:s:w[!fw]", TOYFLAG_USR|TOYFLAG_BIN))

config SEQ
  bool "seq"
  depends on TOYBOX_FLOAT
  default y
  help
    usage: seq [-w|-f fmt_str] [-s sep_str] [first] [increment] last

    Count from first to last, by increment. Omitted arguments default
    to 1. Two arguments are used as first and last. Arguments can be
    negative or floating point.

    -f	Use fmt_str as a printf-style floating point format string
    -s	Use sep_str as separator, default is a newline character
    -w	Pad to equal width with leading zeroes
*/

#define FOR_seq
#include "toys.h"

GLOBALS(
  char *s, *f;

  int precision, buflen;
)

// Ensure there's one %f escape with correct attributes
static void insanitize(char *f)
{
  char *s = next_printf(f, 0);

  if (!s) error_exit("bad -f no %%f");
  if (-1 == stridx("aAeEfFgG", *s) || (s = next_printf(s, 0)))
    error_exit("bad -f '%s'@%d", f, (int)(s-f+1));
}

// Parse a numeric argument setting *prec to the precision of this argument.
// This reproduces the "1.234e5" precision bug from upstream.
static double parsef(char *s)
{
  char *dp = strchr(s, '.');

  if (dp++) TT.precision = maxof(TT.precision, strcspn(dp, "eE"));

  return xstrtod(s);
}

// fast integer conversion to decimal string
// TODO move to lib?
static char *itoa(char *s, int i)
{
  char buf[16], *ff = buf;
  unsigned n = i;

  if (i<0) {
    *s++ = '-';
    n = -i;
  }
  do *ff++ = '0'+n%10; while ((n /= 10));
  do *s++ = *--ff; while (ff>buf);
  *s++ = '\n';

  return s;
}

static char *flush_toybuf(char *ss)
{
  if (ss-toybuf<TT.buflen) return ss;
  xwrite(1, toybuf, ss-toybuf); 

  return toybuf;
}

void seq_main(void)
{
  char fbuf[32], *ss;
  double first = 1, increment = 1, last, dd;
  int ii, inc = 1, len, slen;

  // parse arguments
  if (!TT.s) TT.s = "\n";
  switch (toys.optc) {
    case 3: increment = parsef(toys.optargs[1]);
    case 2: first = parsef(*toys.optargs);
    default: last = parsef(toys.optargs[toys.optc-1]);
  }

  // measure arguments
  if (FLAG(f)) insanitize(TT.f);
  for (ii = len = 0; ii<3; ii++) {
    dd = (double []){first, increment, last}[ii];
    len = maxof(len, snprintf(0, 0, "%.*f", TT.precision, fabs(dd)));
    if (ii == 2) dd += increment;
    slen = dd;
    if (dd != slen) inc = 0;
  }
  if (!FLAG(f)) sprintf(TT.f = fbuf, "%%0%d.%df", len, TT.precision);
  TT.buflen = sizeof(toybuf) - 32 - len - TT.precision - strlen(TT.s);
  if (TT.buflen<0) error_exit("bad -s");

  // fast path: when everything fits in an int with no flags.
  if (!toys.optflags && inc) {
    ii = first;
    len = last;
    inc = increment;
    ss = toybuf;
    if (inc>0) for (; ii<=len; ii += inc)
      ss = flush_toybuf(itoa(ss, ii));
    else if (inc<0) for (; ii>=len; ii += inc)
      ss = flush_toybuf(itoa(ss, ii));
    if (ss != toybuf) xwrite(1, toybuf, ss-toybuf);

    return;
  }

  // Other implementations output nothing if increment is 0 and first > last,
  // but loop forever if first < last or even first == last. We output
  // nothing for all three, if you want endless output use "yes".
  if (!increment) return;

  // Slow path, floating point and fancy sprintf() patterns
  for (ii = 0, ss = toybuf;; ii++) {
    // Multiply to avoid accumulating rounding errors from increment.
    dd = first+ii*increment;
    if ((increment<0 && dd<last) || (increment>0 && dd>last)) break;
    if (ii) ss = flush_toybuf(stpcpy(ss, TT.s));
    ss += sprintf(ss, TT.f, dd);
  }
  *ss++ = '\n';
  xwrite(1, toybuf, ss-toybuf);
}