1 /* set-fields.c -- common functions for parsing field list
2 Copyright (C) 2015-2025 Free Software Foundation, Inc.
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.
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.
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/>. */
17 /* Extracted from cut.c by Assaf Gordon */
25 #include "set-fields.h"
27 /* Array of `struct field_range_pair' holding all the finite ranges. */
28 struct field_range_pair
*frp
;
30 /* Number of finite ranges specified by the user. */
33 /* Number of `struct field_range_pair's allocated. */
34 static idx_t n_frp_allocated
;
36 #define FATAL_ERROR(Message) \
39 error (0, 0, (Message)); \
40 usage (EXIT_FAILURE); \
44 /* Append LOW, HIGH to the list RP of range pairs, allocating additional
45 space if necessary. Update global variable N_FRP. When allocating,
46 update global variable N_FRP_ALLOCATED. */
48 add_range_pair (uintmax_t lo
, uintmax_t hi
)
50 if (n_frp
== n_frp_allocated
)
51 frp
= xpalloc (frp
, &n_frp_allocated
, 1, -1, sizeof *frp
);
58 /* Comparison function for qsort to order the list of
59 struct field_range_pairs. */
61 compare_ranges (const void *a
, const void *b
)
63 struct field_range_pair
const *ap
= a
, *bp
= b
;
64 return (ap
->lo
> bp
->lo
) - (ap
->lo
< bp
->lo
);
67 /* Reallocate Range Pair entries, with corresponding
68 entries outside the range of each specified entry. */
73 struct field_range_pair
*c
= frp
;
81 add_range_pair (1, c
[0].lo
- 1);
83 for (idx_t i
= 1; i
< n
; ++i
)
85 if (c
[i
- 1].hi
+ 1 == c
[i
].lo
)
88 add_range_pair (c
[i
- 1].hi
+ 1, c
[i
].lo
- 1);
91 if (c
[n
- 1].hi
< UINTMAX_MAX
)
92 add_range_pair (c
[n
- 1].hi
+ 1, UINTMAX_MAX
);
97 /* Given the list of field or byte range specifications FIELDSTR,
98 allocate and initialize the FRP array. FIELDSTR should
99 be composed of one or more numbers or ranges of numbers, separated
100 by blanks or commas. Incomplete ranges may be given: '-m' means '1-m';
101 'n-' means 'n' through end of line.
102 n=0 and n>=UINTMAX_MAX values will trigger an error.
104 if SETFLD_ALLOW_DASH option is used, a single '-' means all fields
105 (otherwise a single dash triggers an error).
107 if SETFLD_COMPLEMENT option is used, the specified field list
108 is complemented (e.g. '1-3' will result in fields '4-').
110 if SETFLD_ERRMSG_USE_POS option is used, error messages
111 will say 'position' (or 'byte/character positions')
112 instead of fields (used with cut -b/-c).
114 The function terminates on failure.
116 Upon return, the FRP array is initialized to contain
117 a non-overlapping, increasing list of field ranges.
119 N_FRP holds the number of field ranges in the FRP array.
121 The first field is stored as 1 (zero is not used).
122 An open-ended range (i.e., until the last field of the input line)
123 is indicated with hi = UINTMAX_MAX.
125 A sentinel of UINTMAX_MAX/UINTMAX_MAX is always added as the last
129 given '1-2,4', frp = [ { .lo = 1, .hi = 2 },
130 { .lo = 4, .hi = 4 },
131 { .lo = UINTMAX_MAX, .hi = UINTMAX_MAX } ];
133 given '3-', frp = [ { .lo = 3, .hi = UINTMAX_MAX },
134 { .lo = UINTMAX_MAX, .hi = UINTMAX_MAX } ];
137 set_fields (char const *fieldstr
, unsigned int options
)
139 uintmax_t initial
= 1; /* Value of first number in a range. */
140 uintmax_t value
= 0; /* If nonzero, a number being accumulated. */
141 bool lhs_specified
= false;
142 bool rhs_specified
= false;
143 bool dash_found
= false; /* True if a '-' is found in this field. */
145 bool in_digits
= false;
147 /* Collect and store in RP the range end points. */
149 /* Special case: '--field=-' means all fields, emulate '--field=1-' . */
150 if ((options
& SETFLD_ALLOW_DASH
) && STREQ (fieldstr
,"-"))
153 lhs_specified
= true;
160 if (*fieldstr
== '-')
163 /* Starting a range. */
165 FATAL_ERROR ((options
& SETFLD_ERRMSG_USE_POS
)
166 ? _("invalid byte or character range")
167 : _("invalid field range"));
172 if (lhs_specified
&& !value
)
173 FATAL_ERROR ((options
& SETFLD_ERRMSG_USE_POS
)
174 ? _("byte/character positions are numbered from 1")
175 : _("fields are numbered from 1"));
177 initial
= (lhs_specified
? value
: 1);
180 else if (*fieldstr
== ','
181 || isblank (to_uchar (*fieldstr
)) || *fieldstr
== '\0')
184 /* Ending the string, or this field/byte sublist. */
189 if (!lhs_specified
&& !rhs_specified
)
191 /* if a lone dash is allowed, emulate '1-' for all fields */
192 if (options
& SETFLD_ALLOW_DASH
)
195 FATAL_ERROR (_("invalid range with no endpoint: -"));
198 /* A range. Possibilities: -n, m-n, n-.
199 In any case, 'initial' contains the start of the range. */
202 /* 'n-'. From 'initial' to end of line. */
203 add_range_pair (initial
, UINTMAX_MAX
);
207 /* 'm-n' or '-n' (1-n). */
209 FATAL_ERROR (_("invalid decreasing range"));
211 add_range_pair (initial
, value
);
217 /* A simple field number, not a range. */
219 FATAL_ERROR ((options
& SETFLD_ERRMSG_USE_POS
)
220 ? _("byte/character positions are numbered from 1")
221 : _("fields are numbered from 1"));
223 add_range_pair (value
, value
);
227 if (*fieldstr
== '\0')
231 lhs_specified
= false;
232 rhs_specified
= false;
234 else if (c_isdigit (*fieldstr
))
236 /* Record beginning of digit string, in case we have to
237 complain about it. */
238 static char const *num_start
;
239 if (!in_digits
|| !num_start
)
240 num_start
= fieldstr
;
248 /* Detect overflow. */
249 if (!DECIMAL_DIGIT_ACCUMULATE (value
, *fieldstr
- '0')
250 || value
== UINTMAX_MAX
)
252 /* In case the user specified -c$(echo 2^64|bc),22,
253 complain only about the first number. */
254 char *bad_num
= ximemdup0 (num_start
,
255 strspn (num_start
, "0123456789"));
256 error (0, 0, (options
& SETFLD_ERRMSG_USE_POS
)
257 ?_("byte/character offset %s is too large")
258 :_("field number %s is too large"),
261 usage (EXIT_FAILURE
);
268 error (0, 0, (options
& SETFLD_ERRMSG_USE_POS
)
269 ?_("invalid byte/character position %s")
270 :_("invalid field value %s"),
272 usage (EXIT_FAILURE
);
277 FATAL_ERROR ((options
&SETFLD_ERRMSG_USE_POS
)
278 ? _("missing list of byte/character positions")
279 : _("missing list of fields"));
281 qsort (frp
, n_frp
, sizeof (frp
[0]), compare_ranges
);
283 /* Merge range pairs (e.g. `2-5,3-4' becomes `2-5'). */
284 for (idx_t i
= 0; i
< n_frp
; ++i
)
286 for (idx_t j
= i
+ 1; j
< n_frp
; ++j
)
288 if (frp
[j
].lo
<= frp
[i
].hi
)
290 frp
[i
].hi
= MAX (frp
[j
].hi
, frp
[i
].hi
);
291 memmove (frp
+ j
, frp
+ j
+ 1, (n_frp
- j
- 1) * sizeof *frp
);
300 if (options
& SETFLD_COMPLEMENT
)
303 /* After merging, reallocate RP so we release memory to the system.
304 Also add a sentinel at the end of RP, to avoid out of bounds access
305 and for performance reasons. */
307 frp
= xrealloc (frp
, n_frp
* sizeof (struct field_range_pair
));
308 frp
[n_frp
- 1].lo
= frp
[n_frp
- 1].hi
= UINTMAX_MAX
;