fields.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include "fields.h"
/*
* fields
*
* Overview
* By a field, we mean the various delimited character sequences within each
* line of the input files. The sort key consists of an ordered sequence of
* fields, which need not include all possible fields for the given line.
* (Furthermore, not every line need contain sufficient fields for the fields
* given within the sort key. In fact, none of the lines in the input stream
* need contain sufficient fields.)
*
* There are two methods for specifying fields for sort(1); these are
* discussed in options.c. Here we discuss only the internal representation
* of fields, as used for constructing the collation vector for each line as
* defined by the sort key.
*
* Representation
* The sort key is a singly-linked list of field specifiers. At present,
* fields may belong to one of three species: alphabetical, numerical, or
* monthly; the species (f_species) then indicates the conversion function
* (f_convert) used to transform the raw characters of the character sequence
* to a collatable form. (In principle, this allows us to consider future
* field species such as hexadecimal.)
*
* Fields and offsets are numbered such that zero refers to the first field or
* character, respectively. Thus, the interpretation of a key specifier, m.n,
* is that the field begins at the nth character beyond the mth occurence of
* the key separator. If the blanks flag has been specified, then the field
* begins at the nth non-blank character past the mth key separator. If the
* key separator is unspecified, then the key separator is defined as one or
* more blank characters.
*
* In general, the various options afforded by sort may be broken into two
* categories: field species and field modifiers. For each field species,
* there is one or more conversion routines that take a delimited character
* sequence and convert it to a character sequence collatable by strcmp() or
* memcmp(). For field species that may be further modified, such as the
* fold-to-uppercase option for alphabetic fields, the conversion routine may
* be aware of how the modifier affects collation. Finally, the no-modifiers
* case may present an opportunity for a simplified, faster version.
*
* Code Structure
* The code paths for single-byte and multi-byte locales diverge significantly
* in fields.c. Most routines have an *_wide() version, which produces an
* equivalent effect for line records whose data field is composed of wide
* characters (wchar_t). However, the l_collated field of a line record is
* always composed of characters, so that the radix sorts provided in
* internal.c can work in both single- and multi-byte locales. Thus, in the
* various convert_*_wide() routines, the output is placed in l_collated, with
* a length multiplier of 4.
*/
#define BEFORE_NUMBER 0x0
#define IN_NUMBER 0x1
static char numerical_separator;
static char numerical_decimal;
static char monetary_separator;
static char monetary_decimal;
static wchar_t w_numerical_separator;
static wchar_t w_numerical_decimal;
static wchar_t w_monetary_separator;
static wchar_t w_monetary_decimal;
#define MONTHS_IN_YEAR 12
#define MAX_MON_LEN 20
static char *months[MONTHS_IN_YEAR];
#define DECIMAL_CHAR (numerical_decimal)
#define IS_SEPARATOR(x) \
#define IS_DECIMAL(x) \
((x) == numerical_decimal || \
#define W_DECIMAL_CHAR (w_numerical_decimal)
#define W_IS_SEPARATOR(x) \
#define W_IS_DECIMAL(x) \
(((x) == w_numerical_decimal) || \
#define INTERFIELD_SEPARATOR '\0'
#define W_INTERFIELD_SEPARATOR L'\0'
#define INT_SIGN_FLIP_MASK 0x80000000
#define INT_SIGN_PASS_MASK 0x00000000
/*
* strx_ops_t, xfrm_len, and xfrm_cpy: In the case where we are sorting in the
* C locale, we want to avoid the expense of transforming strings to collatable
* forms since, by definition, an arbitrary string in the C locale is already in
* its collatable form. Therefore, we construct a small ops vector (the
* strx_ops) and two wrappers: xfrm_len() to massage the strxfrm(NULL, ...) into
* strlen()-like behaviour, and xfrm_cpy() to make strncpy() appear
* strxfrm()-like.
*/
/*ARGSUSED*/
static size_t
{
}
/*
* The length represented by n includes a null character, so to return the
* correct length we subtract 1. Note that this function is only used by
* field_convert_alpha, and isn't for general use, as it assumes that n is the
* length of s2 plus a null character.
*/
static size_t
{
return (n - 1);
}
/*ARGSUSED*/
static size_t
{
return (len);
}
typedef struct _strx_ops {
} strx_ops_t;
static const strx_ops_t *xfrm_ops;
static void
{
/*
* A locale need not define all of the cases below: only decimal_point
* must be defined. Furthermore, sort(1) has traditionally not used the
* positive_sign and negative_sign, grouping, or currency_symbols (or
* their numeric counterparts, if any).
*/
} else
numerical_separator = '\0';
} else
monetary_separator = '\0';
} else
monetary_decimal = '\0';
}
static void
{
int i;
int j;
struct tm this_month;
const char *c_months[MONTHS_IN_YEAR] = {
"JAN", "FEB", "MAR", "APR", "MAY", "JUN",
"JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
};
if (is_c_locale) {
for (i = 0; i < MONTHS_IN_YEAR; i++) {
}
/*
* We don't need to initialize the wide version of the month
* names.
*/
return;
}
for (i = 0; i < MONTHS_IN_YEAR; i++) {
this_month.tm_mon = i;
"%b", &this_month);
for (j = 0; j < strlen(month_name); j++)
}
}
void
{
if (S->m_c_locale)
else
}
field_t *
{
F->f_start_field = -1;
F->f_start_offset = -1;
F->f_end_field = -1;
F->f_end_offset = -1;
if (S == NULL) {
F->f_options = 0;
} else {
F->f_species = S->m_default_species;
F->f_options = S->m_field_options;
}
return (F);
}
void
field_delete(field_t *F)
{
free(F);
}
/*
* The recursive implementation of field_add_to_chain() given below is
* inappropriate if function calls are expensive, or a truly large number of
* fields are anticipated.
*/
void
{
if (*F == NULL)
*F = A;
else
field_add_to_chain(&((*F)->f_next), A);
}
#ifdef DEBUG
#ifndef _LP64
#define FIELD_FMT \
"\nStart field: %d\tStart offset: %d\nEnd field: %d\tEnd offset: %d\n"
#else /* !_LP64 */
#define FIELD_FMT \
"\nStart field: %ld\tStart offset: %ld\nEnd field: %ld\tEnd offset: %ld\n"
#endif /* !_LP64 */
/*
* field_print is used only for debugging purposes.
*/
void
field_print(field_t *F)
{
int status = 0;
if (F->f_options & FIELD_REVERSE_COMPARISONS) {
status++;
}
if (F->f_options & FIELD_DICTIONARY_ORDER) {
status++;
}
if (F->f_options & FIELD_FOLD_UPPERCASE) {
status++;
}
if (F->f_options & FIELD_IGNORE_NONPRINTABLES) {
status++;
}
if (F->f_options & FIELD_IGNORE_BLANKS_START) {
status++;
}
if (F->f_options & FIELD_IGNORE_BLANKS_END) {
status++;
}
if (status == 0)
F->f_end_field, F->f_end_offset);
}
#endif /* DEBUG */
static ssize_t
{
char *T = S;
char *eol = S + L->l_data_length;
return (L->l_data_length);
while (field-- > 0) {
T++;
T++;
}
while (IS_BLANK(*T))
T++;
}
return (L->l_data_length);
return (ret);
}
static void
{
*start = field_boundary(F, L, 0,
F->f_options & FIELD_IGNORE_BLANKS_END);
}
static ssize_t
{
wchar_t *T = S;
return (L->l_data_length);
while (field-- > 0) {
while (T < eol && W_IS_BLANK(*T))
T++;
while (T < eol && !W_IS_BLANK(*T))
T++;
}
while (W_IS_BLANK(*T))
T++;
}
return (L->l_data_length);
return (ret);
}
static void
{
*start = field_boundary_wide(F, L, 0,
F->f_options & FIELD_IGNORE_BLANKS_END);
}
static ssize_t
{
char *T = S;
char *eol = S + L->l_data_length;
return (L->l_data_length);
while (field-- > 0) {
return (L->l_data_length);
T++;
}
while (IS_BLANK(*T))
T++;
}
return (L->l_data_length);
ret--;
return (ret);
}
/*
* field_delimit_tabbed() is called when a field separator has been defined
* using the -t option. The character at the offset, start, is either one or
* more character positions past the delimiter marking the start of the
* field, or at the end of the line.
*/
static void
{
}
static ssize_t
{
wchar_t *T = S;
return (L->l_data_length);
while (field-- > 0) {
return (L->l_data_length);
T++;
}
while (W_IS_BLANK(*T))
T++;
}
return (L->l_data_length);
ret--;
return (ret);
}
static void
{
}
/*ARGSUSED*/
{
int j;
if (sizeof (char) > L->l_collate_bufsize - coll_offset)
return (-1);
/*
* The month field formally begins with the first non-blank character.
*/
month_offset++;
month_length--;
}
for (j = 0; j < MAX_MON_LEN && j < month_length; j++)
for (j = 0; j < MONTHS_IN_YEAR; j++) {
return (1);
}
}
/*
* no matching month; copy string into field. required behaviour is
* that "month-free" keys sort before month-sortable keys, so insert
* a "will sort first" token.
*/
if (val < 0)
return (-1);
else
return (val + 1);
}
/*ARGSUSED*/
{
ssize_t j;
sizeof (wchar_t))
return (-1);
month_offset++;
month_length--;
}
for (j = 0; j < MAX_MON_LEN && j < month_length; j++)
for (j = 0; j < MONTHS_IN_YEAR; j++)
w_month_lengths[j])) {
return (1);
}
if (val < 0)
return (-1);
else
return (val + 1);
}
/*
* field_convert_alpha() always fails with return value -1 if the converted
* string would cause l_collate_length to exceed l_collate_bufsize
*/
/*ARGSUSED*/
{
static char *compose;
static ssize_t compose_length;
ssize_t i;
}
if ((F->f_options & FIELD_IGNORE_NONPRINTABLES) &&
continue;
if ((F->f_options & FIELD_DICTIONARY_ORDER) &&
continue;
if (F->f_options & FIELD_FOLD_UPPERCASE)
t = toupper(t);
}
L->l_collate_bufsize - coll_offset)
else
return ((ssize_t)-1);
}
/*ARGSUSED*/
{
static char *compose;
static ssize_t compose_length;
}
L->l_collate_bufsize - coll_offset)
else
return ((ssize_t)-1);
}
/*ARGSUSED*/
{
sizeof (wchar_t));
ssize_t i;
continue;
!iswspace(t))
continue;
if (F->f_options & FIELD_FOLD_UPPERCASE)
t = towupper(t);
}
coll_offset * sizeof (wchar_t)) {
} else {
}
return (ret);
}
/*
* field_convert_numeric() converts the given field into a collatable numerical
* sequence. The sequence is ordered as { log, integer, separator, fraction },
* with an optional sentinel component at the sequence end.
*/
/*ARGSUSED*/
{
char *number;
char sign = '2';
int log_ten;
size_t j = 0;
size_t i;
int state = BEFORE_NUMBER;
/*
* Eat leading blanks, if any.
*/
for (i = 0; i < length; i++)
break;
/*
* Test that there is sufficient size in the collation buffer for our
* number. In addition to the possible remaining characters in the
* field, we also require space for the sign (char), logarithm (int),
* separator (char), and as many as two string terminators (for reverse
* sorts).
*/
if (((length - i) + 4 * sizeof (char) + sizeof (int)) >
(L->l_collate_bufsize - coll_offset))
return ((ssize_t)-1);
/*
* If negative, set sign.
*/
if (number[i] == '-') {
i++;
sign = '0';
}
/*
* Scan integer part; eat leading zeros.
*/
for (; i < length; i++) {
if (IS_SEPARATOR(number[i]))
continue;
continue;
break;
if (sign == '0')
else
}
/*
* Integer part terminated by decimal.
*/
digits[j] = DECIMAL_CHAR;
log_ten = j++;
/*
* Scan fractional part.
*/
for (++i; i < length; i++) {
if (IS_SEPARATOR(number[i]))
continue;
break;
if (number[i] != '0')
if (sign == '0')
else
}
if (sign == '0')
} else {
/*
* Nondigit or end of string seen.
*/
log_ten = (int)j;
if (sign == '0')
else
digits[j] = INTERFIELD_SEPARATOR;
}
/*
* A non-zero number was not detected; treat as defined zero.
*/
sign = '1';
log_ten = 0;
digits[0] = '0';
j = 1;
}
/*
* We subtract a constant from the log of negative values so that
* they will correctly precede positive values with a zero logarithm.
*/
if (sign == '0') {
if (j != 0)
else
/*
* Special case for -0.
*/
log_ten = -1;
}
/*
* Place logarithm in big-endian form.
*/
for (i = 0; i < sizeof (int); i++)
>> ((sizeof (int) - 1) * NBBY);
if (j + sizeof (char) + sizeof (int) <
L->l_collate_bufsize - coll_offset)
return (j + 1 + sizeof (int));
else
return ((ssize_t)-1);
}
/*ARGSUSED*/
{
char *lbuffer;
int log_ten;
size_t j = 0;
size_t i;
int state = BEFORE_NUMBER;
for (i = 0; i < length; i++)
if (!W_IS_BLANK(number[i]))
break;
sizeof (int)) > (L->l_collate_bufsize - coll_offset))
return ((ssize_t)-1);
if (number[i] == L'-') {
i++;
sign = L'0';
}
for (; i < length; i++) {
if (W_IS_SEPARATOR(number[i]))
continue;
continue;
break;
if (sign == L'0')
else
}
digits[j] = W_DECIMAL_CHAR;
log_ten = j++;
for (++i; i < length; i++) {
if (W_IS_SEPARATOR(number[i]))
continue;
break;
if (number[i] != L'0')
if (sign == L'0')
else
}
if (sign == L'0')
} else {
log_ten = (int)j;
if (sign == L'0')
else
digits[j] = W_INTERFIELD_SEPARATOR;
}
sign = L'1';
log_ten = 0;
digits[0] = L'0';
j = 1;
}
if (sign == L'0') {
if (j != 0)
else
log_ten = -1;
}
/*
* Place logarithm in big-endian form.
*/
for (i = 0; i < sizeof (int); i++)
>> ((sizeof (int) - 1) * NBBY);
return (j + 1 + sizeof (int) / sizeof (wchar_t));
else
return ((ssize_t)-1);
}
/*
* flags contains one of CV_REALLOC, CV_FAIL, specifying the preferred behaviour
* when coll_offset exceeds l_collate_bufsize.
*/
{
ssize_t coll_offset = 0;
field_t *cur_fieldp = F;
while (cur_fieldp != NULL) {
/*
* delimit field
*/
if (!field_separator.sc)
else
distance = 0;
/*
* Convert field, appending to collated field of line
* record.
*/
/*
* branch should execute comparatively rarely
*/
if (distance == -1) {
if (flags & FCV_REALLOC) {
ASSERT(L->l_collate_bufsize > 0);
L->l_collate_bufsize *= 2;
L->l_collate_bufsize);
continue;
} else {
/*
* FCV_FAIL has been set.
*/
return (-1);
}
}
}
(char)(UCHAR_MAX - INTERFIELD_SEPARATOR);
distance++;
}
coll_offset += distance;
if (coll_offset >= L->l_collate_bufsize) {
if (flags & FCV_REALLOC) {
ASSERT(L->l_collate_bufsize > 0);
L->l_collate_bufsize *= 2;
L->l_collate_bufsize);
} else {
return (-1);
}
}
coll_offset++;
}
L->l_collate_length = coll_offset;
return (L->l_collate_length);
}
{
ssize_t coll_offset = 0;
field_t *cur_fieldp = F;
while (cur_fieldp != NULL) {
if (!field_separator.wc)
else
distance = 0;
if (distance == -1) {
if (flags & FCV_REALLOC) {
ASSERT(L->l_collate_bufsize > 0);
L->l_collate_bufsize *= 2;
L->l_collate_bufsize);
continue;
} else {
return (-1);
}
}
}
distance++;
}
coll_offset += distance;
if (flags & FCV_REALLOC) {
ASSERT(L->l_collate_bufsize > 0);
L->l_collate_bufsize *= 2;
L->l_collate_bufsize);
} else {
return (-1);
}
}
coll_offset++;
}
#ifdef _LITTLE_ENDIAN
#endif /* _LITTLE_ENDIAN */
return (L->l_collate_length);
}
/*
* line_convert() and line_convert_wide() are called when the collation vector
* of a given line has been exhausted, and we are performing the final,
* full-line comparison required by the sort specification. Because we do not
* have a guarantee that l_data is null-terminated, we create an explicitly
* null-terminated copy suitable for transformation to a collatable form for the
* current locale.
*/
static void
{
static char *buffer;
return;
}
}
static void
{
return;
sizeof (wchar_t));
}
sizeof (wchar_t));
}
/*
* Our convention for collation is
*
* A > B => r > 0,
* A == B => r = 0,
* A < B => r < 0
*
* This convention is consistent with the definition of memcmp(), strcmp(), and
* strncmp() in the C locale. collated() and collated_wide() have two optional
* behaviours, which can be activated by setting the appropriate values in
* coll_flag: COLL_UNIQUE, which returns 0 if the l_collate fields of the line
* records being compared are identical; COLL_DATA_ONLY, which ignores the
* l_collate field for the current comparison; and COLL_REVERSE, which flips the
* result for comparisons that fall through to an actual data comparison (since
* the collated vector should already reflect reverse ordering from field
* conversion).
*/
int
{
int r;
if (!(coll_flag & COLL_DATA_ONLY)) {
if (ml > 0) {
if (r)
return (r);
}
if (A->l_collate_length < B->l_collate_length)
return (-1);
if (A->l_collate_length > B->l_collate_length)
return (1);
}
/*
* This is where we cut out, if we know that the current sort is over
* the entire line.
*/
if (coll_flag & COLL_UNIQUE)
return (0);
line_convert(A);
line_convert(B);
if (r)
return (r ^ mask);
return (-1 ^ mask);
return (1 ^ mask);
return (0);
}
int
{
int r;
if (!(coll_flag & COLL_DATA_ONLY)) {
if (ml > 0) {
if (r)
return (r);
}
if (A->l_collate_length < B->l_collate_length)
return (-1);
if (A->l_collate_length > B->l_collate_length)
return (1);
}
if (coll_flag & COLL_UNIQUE)
return (0);
if (r)
return (r ^ mask);
return (-1 ^ mask);
return (1 ^ mask);
return (0);
}