/* relational.c:
*
* vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
****************************************************************
* Copyright (C) 2003 Tom Lord
*
* See the file "COPYING" for further information about
* the copyright and warranty status of this work.
*/
#include "hackerlab/bugs/panic.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/os/errno.h"
#include "hackerlab/os/stdarg.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/vu/safe-vu-utils-vfdbuf.h"
#include "hackerlab/mem/talloc.h"
#include "hackerlab/sort/qsort.h"
#include "hackerlab/char/char-class.h"
#include "hackerlab/char/str.h"
#include "hackerlab/char/pika-escaping-utils.h"
#define _IN_RELATIONAL_
#include "libawk/relational.h"
AR_TYPEDEF (int, rel_cut_spec);
AR_TYPEDEF (rel_field, rel_record);
AR_TYPEDEF (rel_record, rel_table);
AR_TYPEDEF (struct rel_join_output_spec, rel_join_output_spec);
AR_TYPEDEF (struct rel_join_output_spec *, rel_join_output_specs);
/* __STDC__ prototypes for static functions */
static int rec_cmp_by_field (void * va, void * vb, void * vdata);
static int rec_cmp_by_field_fn (void * va, void * vb, void * vdata);
static int rec_cmp_by_fields (void * va, void * vb, void * vdata);
static rel_record rel_read_record (int fd,
int n_fields,
char * err_name,
char * err_src,
char const * name);
static rel_record rel_read_pika_unescape_iso8859_1_record (int fd,
int n_fields,
char * err_name,
char * err_src,
char const * name);
static void rel_print_record (int fd, rel_record rec);
static void rel_print_pika_escape_iso8859_1_record (int fd, int escape_classes, rel_record rec);
static void rel_print_record_sp (int fd, rel_record rec);
static void rel_print_pika_escape_iso8859_1_record_sp (int fd, int escape_classes, rel_record rec);
/************************************************************************
*(h0 "Relational Tables")
*
* Tla makes heavy use of a simple data structure for storing two
* dimensional tables of strings: the `rel_table' type.
*
* In general, these functions will cause the process to exit with
* non-0 status and a error message to the standard error descriptor
* if an allocation failure occurs.
*/
/*(menu)
*/
/************************************************************************
*(h1 "Table Types")
*
* Tables should be declared to be of type `rel_table' and initialized
* to 0, as in:
*
* rel_table table = 0;
*
*
* Individual records (each an array of fields) and individual fields
* can be read using ordinary, 0-based array subscripting:
*
* table[4][1]
*
* refers to the second field (or column) of the fifth row (or record)
* of `tabel'.
*/
/*(c rel_field :category type)
* typedef t_uchar * rel_field;
*
* A single field within a relational table -- a 0-terminated string.
*/
/*(c rel_record :category type)
* typedef rel_field * rel_record;
*
* A single row within a relational table; an array of fields.
*/
/*(c rel_table :category type)
* typedef rel_record * rel_table;
*
* A relational table; an array of records.
*/
/************************************************************************
*(h1 "Table Sizes")
*
*/
/*(c rel_n_records)
* int rel_n_records (rel_table r);
*
* Return the number of records (rows) within a table.
*/
int
rel_n_records (rel_table r)
{
return ar_size_rel_table (r);
}
/*(c rel_n_fields)
* int rel_n_fields (rel_record r);
*
* Return the number of fields (columns) within a record.
*/
int
rel_n_fields (rel_record r)
{
return ar_size_rel_record (r);
}
/************************************************************************
*(h1 "Adding Fields and Records")
*
*
*
*/
/*(c rel_make_record)
* rel_record rel_make_record (t_uchar const * const field0, ...);
*
* Allocate a new record containing the indicated fields.
*
* The list of field values may not itself contain a 0 (null) field
* and must be terminated by a 0, as in this call:
*
* r = rel_make_record ("apples", "oranges", 0);
*
* which creates a record with two fields.
*
* Note that this function allocates private copies of the fields.
* They will be freed by `ar_free_rel_record' or `rel_free_table'.
*/
rel_record
_rel_make_record (char const * name, t_uchar const * const field0, ...)
{
va_list fp;
rel_record answer;
if (!field0)
return 0;
answer = 0;
_rel_add_field (&answer, field0, name);
va_start (fp, field0);
while (1)
{
t_uchar * contents;
contents = va_arg (fp, t_uchar *);
if (!contents)
break;
rel_add_field (&answer, contents);
}
va_end (fp);
return answer;
}
void
_rel_add_record (rel_table * table, rel_record r, char const * name)
{
ar_push_ext_rel_table (table, r, name);
talloc_steal (ar_base (*table), ar_base (r));
}
/*(c rel_add_records)
* void rel_add_records (rel_table * table, ...);
*
* Append records to `*table'.
*
* The list of records must be terminated by 0 (null).
*
* This procedure may move the table itself in memory. If
* it does, `*table' will be updated to point to the relocated
* table.
*
* This procedure does ^not^ copy its argument records but uses
* them directly. If the table is later passed to `rel_free_table',
* those records will be freed. (Thus, in general, tables should
* not share records.)
*
* A typical usage, creating a table of the form:
*
* apples trees
* oranges trees
* grapes vines
*
* is the call (not with care the 0 values passed to terminate
* argument lists):
*
* rel_table t = 0;
*
* rel_add_records (&t, rel_make_record ("apples", "trees", 0),
* rel_make_record ("oranges", "trees", 0),
* rel_make_record ("grapes", "vines", 0),
* 0);
*/
void
_rel_add_records (rel_table * table, char const * name, ...)
{
va_list rp;
rel_record r;
va_start (rp, name);
for (r = va_arg (rp, rel_record); r; r = va_arg (rp, rel_record))
_rel_add_record (table, r, name);
va_end (rp);
}
/**
* \brief insert records into a rel_table.
*
* if the index is not <= rel_n_records table, an assertion will trigger
* \param table the table to insert into
* \param index the line to insert at
* \param record, ...
* \return void
*/
void
rel_insert_records (rel_table *table, int index, ...)
{
va_list rp;
rel_record r;
invariant (index <= rel_n_records (*table));
va_start (rp, index);
for (r = va_arg (rp, rel_record); r; r = va_arg (rp, rel_record))
{
ar_insert_rel_table (table, index, r);
}
va_end (rp);
}
/**
* \brief replace a record in a rel table
* \param table the table to replace in
* \param index the line to replace
* \param record the line to replace with
* \return void
*/
void
rel_replace_record (rel_table table, int index, rel_record record)
{
ar_free_rel_record (&table[index]);
table[index] = record;
talloc_steal (ar_base (table), ar_base (record));
}
/**
* \brief remove records from a rel_table.
*
* \param table the table to remove from
* \param from_index the first record to remove
* \param to_index the last record to remove
* \return void
*/
void
rel_remove_records (rel_table *table, int from_index, int to_index)
{
int index;
if (from_index > rel_n_records (*table) - 1)
from_index = rel_n_records (*table) - 1;
if (to_index > rel_n_records (*table) - 1)
to_index = rel_n_records (*table) - 1;
if (from_index > to_index)
to_index = from_index;
if (from_index < 0)
return;
/* free resources */
for (index = from_index; index < to_index + 1; ++index)
{
ar_free_rel_record (&(*table)[index]);
}
/* now shrink the table */
for (index = from_index; index < to_index + 1; ++index)
{
ar_remove_rel_table (table, from_index);
}
}
/*(c rel_add_field)
* void rel_add_field (rel_record * r, t_uchar const * field);
*
* Append a single field (the string `field') to the record `*r'.
*
* A private copy of `field' is allocated.
*
* The record may be relocated in memory in which case the value of
* `*r' will be update.
*/
void
_rel_add_field (rel_record * r, t_uchar const * field, char const * name)
{
if (*r)
ar_push_rel_record (r, talloc_strdup (ar_base(*r), field));
else
{
ar_push_ext_rel_record (r, talloc_strdup (NULL, field), name);
talloc_steal (ar_base (*r), (*r)[ar_size_rel_record (*r) - 1]);
}
}
/*(c rel_singleton_record_n)
* rel_record rel_singleton_record_n (t_uchar const * start, size_t len);
*
* Create a new record containing a single field which is a copy of
* the `len' characters beginning at `start' with a final 0 appended.
*/
rel_record
rel_singleton_record_n (t_uchar const * start, size_t len)
{
rel_record answer = 0;
ar_push_rel_record (&answer, talloc_strndup (NULL, start, len));
talloc_steal (ar_base (answer), answer[0]);
return answer;
}
/************************************************************************
*(h1 "Freeing Records and Tables")
*
*
*
*/
/*(c rel_free_table)
* void rel_free_table (rel_table t);
*
* Free the entire table `t'.
*
* This function will also free all records which are part
* of `t' -- there is no need to separately call `ar_free_rel_record'.
*/
void
rel_free_table (rel_table t)
{
ar_free_rel_table (&t);
}
/************************************************************************
*(h1 "Parsing Tables from Strings")
*
*
*
*/
typedef int split_callback(void const * , char);
/*(c rel_callback_split)
* rel_table rel_ws_split (t_uchar * string, split_callback *split_fn, void * split_context);
*
* Allocate and return a new table formed by parsing rows,
* each containing a single field, from `string'. Rows
* are separated by arbitrary whitespace.
*/
static rel_table
rel_callback_split (t_uchar const * string, split_callback * split_fn, void const *split_context)
{
rel_table answer = 0;
t_uchar const * start;
t_uchar const * end;
if (!string)
return 0;
start = string;
while (1)
{
while (split_fn (split_context, *start))
++start;
if (!*start)
return answer;
end = start;
while (*end && !split_fn (split_context, *end))
++end;
rel_add_records (&answer, rel_singleton_record_n ((t_uchar *)start, end - start), 0);
start = end;
}
}
static int
split_delim (void const * context, char ch)
{
t_uchar const *delimiters = (t_uchar const * ) context;
while (*delimiters != '\0')
if (*delimiters++ == ch)
return 1;
return 0;
}
/*(c rel_delim_split)
* rel_table rel_delim_split (t_uchar * string, t_uchar * delimiters);
*
* Allocate and return a new table formed by parsing rows,
* each containing a single field, from `string'. Rows
* are separated by any char in delimiters.
*/
rel_table
rel_delim_split (t_uchar const * string, t_uchar const * delimiters)
{
return rel_callback_split (string, split_delim, delimiters);
}
static int
split_is_space (void const *unused, char ch)
{
return char_is_space (ch);
}
/*(c rel_ws_split)
* rel_table rel_ws_split (t_uchar const * string);
*
* Allocate and return a new table formed by parsing rows,
* each containing a single field, from `string'. Rows
* are separated by arbitrary whitespace.
*/
rel_table
rel_ws_split (t_uchar const * string)
{
return rel_callback_split (string, split_is_space, NULL);
}
/*(c rel_nl_split)
* rel_table rel_nl_split (t_uchar const * string);
*
* Allocate and return a new table formed by parsing rows,
* each containing a single field, from `string'. Rows
* are separated by newlines.
* FIXME: audit the callers to this to see if it can be integrated
* into the callback method above, which would strip empty lines.
*/
rel_table
rel_nl_split (t_uchar const * string)
{
rel_table answer = 0;
t_uchar const * start;
t_uchar const * end;
if (!string)
return 0;
start = string;
while (1)
{
if (!*start)
return answer;
end = start;
while (*end && (*end != '\n'))
++end;
rel_add_records (&answer, rel_singleton_record_n (start, end - start), 0);
if (*end)
start = end + 1;
else
start = end;
}
}
/**
* \brief convert a string of whitespace separated tokens to a two column table
* Throws EINVAL if the token count is not even.
* \param string the input
* \return rel_table the result.
*/
rel_table
rel_ws_split_pairs (t_uchar const * string)
{
rel_table answer = NULL;
rel_table singles = NULL;
int index = 0;
int lim = 0;
singles = rel_ws_split (string);
lim = rel_n_records (singles);
/* Make sure there is an even number of entries. */
if (lim % 2)
Throw (exception (EINVAL, "rel_ws_split_pairs: string has an uneven number of tokens"));
for (index = 0; index < lim / 2; ++index)
rel_add_records (&answer,
rel_make_record (singles[index][0], singles[index + 1][0], 0),
0);
rel_free_table (singles);
return answer;
}
/************************************************************************
*(h1 "Copying Tables")
*
*
*
*/
/*(c rel_copy_table)
* rel_table rel_copy_table (rel_table t);
*
* Return a freshly allocated copy of table `t'.
*/
rel_table
_rel_copy_table (rel_table t, char const * name)
{
rel_table answer;
int records;
int r;
records = rel_n_records (t);
answer = 0;
ar_setsize_ext_rel_table (&answer, records, name);
for (r = 0; r < records; ++r)
{
answer[r] = _rel_copy_record (t[r], name);
talloc_steal (ar_base (answer), ar_base (answer[r]));
}
return answer;
}
/*(c rel_copy_record)
* rel_record rel_copy_record (rel_record r);
*
* Return a freshly allocated copy of record `r'.
*/
rel_record
_rel_copy_record (rel_record r, char const * name)
{
rel_record answer;
int fields;
int f;
fields = rel_n_fields (r);
answer = 0;
ar_setsize_ext_rel_record (&answer, fields, name);
for (f = 0; f < fields; ++f)
answer[f] = talloc_reference (ar_base (answer), r[f]);
return answer;
}
/************************************************************************
*(h1 "Appending Tables")
*
*
*
*/
/*(c rel_append_x)
* void rel_append_x (rel_table * out, rel_table t);
*
* Append copies of all records in table `t' to the
* table `*out'.
*
* This procedure may move the output table in memory. If
* it does, `*out' will be updated to point to the relocated
* table.
*/
void
_rel_append_x (rel_table * out, rel_table t, char const * name)
{
int lim;
int x;
lim = rel_n_records (t);
for (x = 0; x < lim; ++x)
_rel_add_record (out, rel_copy_record (t[x]), name);
}
/************************************************************************
*(h1 "Reordering Tables")
*
*
*
*/
/*(c rel_reverse_table)
* void rel_reverse_table (rel_table t);
*
* Reverse the order of records in table `t'.
*/
void
rel_reverse_table (rel_table t)
{
int a;
int b;
a = 0;
b = rel_n_records (t) - 1;
while (a < b)
{
rel_record tmp;
tmp = t[a];
t[a] = t[b];
t[b] = tmp;
++a;
--b;
}
}
struct rel_sort_spec
{
int reverse_p;
int field;
};
/*(c rel_sort_table_by_field)
* void rel_sort_table_by_field (int reverse_p,
* rel_table t,
* int field_n);
*
* Sort table `t' lexically according the contents of
* `field_n' within each record.
*
* If `reverse_p' is not 0, then sort in descending rather
* than ascending order.
*/
void
rel_sort_table_by_field (int reverse_p,
rel_table t,
int field_n)
{
struct rel_sort_spec spec;
spec.reverse_p = reverse_p;
spec.field = field_n;
quicksort ((void *)t, rel_n_records (t), sizeof (rel_record), rec_cmp_by_field, (void *)&spec);
}
static int
rec_cmp_by_field (void * va, void * vb, void * vdata)
{
rel_record * a;
rel_record * b;
struct rel_sort_spec * spec;
a = (rel_record *)va;
b = (rel_record *)vb;
spec = (struct rel_sort_spec *)vdata;
if (spec->reverse_p)
{
return -str_cmp ((*a)[spec->field], (*b)[spec->field]);
}
else
{
return str_cmp ((*a)[spec->field], (*b)[spec->field]);
}
}
struct rel_sort_by_fn_spec
{
int reverse_p;
int field;
int (*fn) (t_uchar * va, t_uchar * vb);
};
/*(c rel_sort_table_by_field_fn)
* void rel_sort_table_by_field_fn (int reverse_p,
* rel_table t,
* int field_n,
* int (*fn)(t_uchar *, t_uchar *));
*
* Sort table `t' according the contents of
* `field_n' within each record.
*
* If `reverse_p' is not 0, then sort in descending rather
* than ascending order.
*
* The ordering is determined by `fn' which should accept
* two arguments, both field values, and return -1, 0, or 1
* depending on whether the first is less than, equal to, or
* greater than the second.
*/
void
rel_sort_table_by_field_fn (int reverse_p,
rel_table t,
int field_n,
int (*fn)(t_uchar *, t_uchar *))
{
struct rel_sort_by_fn_spec spec;
spec.reverse_p = reverse_p;
spec.field = field_n;
spec.fn = fn;
quicksort ((void *)t, rel_n_records (t), sizeof (rel_record), rec_cmp_by_field_fn, (void *)&spec);
}
static int
rec_cmp_by_field_fn (void * va, void * vb, void * vdata)
{
rel_record * a;
rel_record * b;
struct rel_sort_by_fn_spec * spec;
a = (rel_record *)va;
b = (rel_record *)vb;
spec = (struct rel_sort_by_fn_spec *)vdata;
if (spec->reverse_p)
{
return -spec->fn ((*a)[spec->field], (*b)[spec->field]);
}
else
{
return spec->fn ((*a)[spec->field], (*b)[spec->field]);
}
}
struct rel_nsort_spec
{
int reverse_p;
int * fields;
};
/*(c rel_sort_table_by_fields)
* void rel_sort_table_by_fields (int reverse_p,
* rel_table t,
* int * fields);
*
* Sort table `t' lexically, according the contents of the indicated
* fields.
*
* If `reverse_p' is not 0, then sort in descending rather
* than ascending order.
*
* `fields' is a list of fields created by `rel_sort_fields' (see below)
* and lists the sort keys, from highest to lowest priority.
*/
void
rel_sort_table_by_fields (int reverse_p,
rel_table t,
int * fields)
{
struct rel_nsort_spec spec;
spec.reverse_p = reverse_p;
spec.fields = fields;
quicksort ((void *)t, rel_n_records (t), sizeof (rel_record), rec_cmp_by_fields, (void *)&spec);
}
/*(c rel_sort_fields)
* int * rel_sort_fields (int f, ...);
*
* Construct a list of fields suitable for use with
* `rel_sort_table_by_fields'.
*
* The arguments should be terminated by an argument which is less
* than 0.
*
* It is not necessary to free the value returned by this
* function (but ^note^ that, at the moment, the table is
* simply space-leaked).
*/
AR_TYPEDEF(int, legint);
int *
rel_sort_fields (int f, ...)
{
va_list fp;
int * answer;
answer = 0;
ar_push_legint (&answer, f);
va_start (fp, f);
while (1)
{
f = va_arg (fp, int);
ar_push_legint (&answer, f);
if (f < 0)
break;
}
va_end (fp);
return answer;
}
static int
rec_cmp_by_fields (void * va, void * vb, void * vdata)
{
rel_record * a;
rel_record * b;
struct rel_nsort_spec * spec;
int nth;
a = (rel_record *)va;
b = (rel_record *)vb;
spec = (struct rel_nsort_spec *)vdata;
for (nth = 0; spec->fields[nth] >= 0; ++nth)
{
int cmp;
if (spec->reverse_p)
{
cmp = -str_cmp ((*a)[spec->fields[nth]], (*b)[spec->fields[nth]]);
}
else
{
cmp = str_cmp ((*a)[spec->fields[nth]], (*b)[spec->fields[nth]]);
}
if (cmp)
return cmp;
}
return 0;
}
/************************************************************************
*(h1 "Eliminating Duplicate Fields")
*
*
*
*/
/*(c rel_uniq_by_field)
* void rel_uniq_by_field (rel_table * table,
* int field);
*
* Discard from `table' the second and subsequent
* consecutive occurences of contiguous records sharing
* equal values for the indicated `field'.
*
* This procedure may move the `table' in memory. If
* it does, `*table' will be updated to point to the
* relocated table.
*/
void
_rel_uniq_by_field (rel_table * table,
int field, char const * name)
{
int lim;
int dest;
int src;
lim = rel_n_records (*table);
for (dest = 0, src = 0; src < lim; ++dest, ++src)
{
(*table)[dest] = (*table)[src];
while ((src < (lim - 1)) && !str_cmp ((*table)[dest][field], (*table)[src + 1][field]))
{
ar_free_rel_record (&(*table)[src + 1]);
++src;
}
}
ar_setsize_ext_rel_table (table, dest, name);
}
/************************************************************************
*(h1 "Table Cuts")
*
*
*
*/
/*(c rel_cut)
* rel_table rel_cut (rel_cut_spec fields, rel_table t);
*
* Create a new, freshly allocated table formed by removing
* from `t' the fields indicated by `fields'.
*
* `fields' should be a value returned by `rel_cut_list' (see below).
*/
rel_table
_rel_cut (rel_cut_spec fields, rel_table t, char const * name)
{
rel_table answer;
int lim;
int x;
answer = 0;
lim = ar_size_rel_table (t);
for (x = 0; x < lim; ++x)
_rel_add_record (&answer, rel_cut_record (fields, t[x]), name);
return answer;
}
/*(c rel_cut_record)
* rel_record rel_cut_record (rel_cut_spec fields, rel_record r);
*
* Create a new, freshly allocated record formed by removing
* from `r' the fields indicated by `fields'.
*
* `fields' should be a value returned by `rel_cut_list' (see below).
*/
rel_record
rel_cut_record (rel_cut_spec fields, rel_record r)
{
rel_record answer;
int x;
answer = 0;
for (x = 0; fields[x] >= 0; ++x)
{
rel_add_field (&answer, r[fields[x]]);
}
return answer;
}
/*(c rel_cut_list)
* rel_cut_spec rel_cut_list (int field, ...);
*
* Construct a list of fields suitable for use with
* `rel_cut'.
*
* The arguments should be terminated by an argument which is less
* than 0.
*
* It is not necessary to free the value returned by this
* function (but ^note^ that, at the moment, the table is
* simply space-leaked).
*/
rel_cut_spec
rel_cut_list (int field, ...)
{
va_list fp;
rel_cut_spec answer;
answer = 0;
ar_push_rel_cut_spec (&answer, field);
va_start (fp, field);
while (1)
{
field = va_arg (fp, int);
ar_push_rel_cut_spec (&answer, field);
if (field < 0)
break;
}
va_end (fp);
return answer;
}
void
rel_cut_spec_finalise (rel_cut_spec *spec)
{
ar_free_rel_cut_spec (spec);
}
/************************************************************************
*(h1 "The Relational Join Operation")
*
*
*
*/
/*(c rel_join)
* rel_table rel_join (int absence_table,
* struct rel_join_output_spec * output,
* int table1_field,
* int table2_field,
* rel_table table1,
* rel_table table2);
*
* Perform a relational join on `table1' and `table2' as
* specified by the other arguments.
*
* `table1_field' and `table2_field' indicate the fields to compare
* for the join. Both tables should be lexically sorted by that
* field, in increasing order.
*
* If `absence_table' is -1, then the output table contains an entry
* for each row of `table1' and `table2' having the indicated fields
* in common. If `absence_table' is 1, then output is produced only
* for rows unique to table 1, if 2, then for rows unique to table2.
*
* `output' describes which field values to copy into the output table.
* See `rel_join_output' below.
*/
rel_table
_rel_join (int absence_table,
struct rel_join_output_spec * output,
int table1_field,
int table2_field,
rel_table table1,
rel_table table2,
char const * name)
{
int f1_len;
int f2_len;
int f1_pos;
int f2_pos;
int n_output_fields;
rel_table answer;
/* How curious that such a simple loop can do so many useful things.
*/
answer = 0;
f1_len = rel_n_records (table1);
f2_len = rel_n_records (table2);
for (n_output_fields = 0; output[n_output_fields].table != -1; ++n_output_fields)
;
f1_pos = 0;
f2_pos = 0;
while ((f1_pos < f1_len) || (f2_pos < f2_len))
{
int cmp;
int want_output;
if (f2_pos == f2_len)
cmp = -1;
else if (f1_pos == f1_len)
cmp = 1;
else
cmp = str_cmp (table1[f1_pos][table1_field], table2[f2_pos][table2_field]);
if (absence_table < 0)
want_output = !cmp;
else if (absence_table == 1)
want_output = (cmp < 0);
else
want_output = (cmp > 0);
if (want_output)
{
rel_record r;
rel_record f1_rec;
rel_record f2_rec;
int x;
r = 0;
f1_rec = ((f1_pos < f1_len) ? table1[f1_pos] : 0);
f2_rec = ((f2_pos < f2_len) ? table2[f2_pos] : 0);
for (x = 0; x < n_output_fields; ++x)
{
_rel_add_field (&r, ((output[x].table == 1) ? f1_rec : f2_rec)[output[x].field], name);
}
_rel_add_record (&answer, r, name);
}
if ((f1_pos < f1_len) && (cmp <= 0))
++f1_pos;
if ((f2_pos < f2_len) && (cmp >= 0))
++f2_pos;
}
return answer;
}
/*(c rel_join_output)
* struct rel_join_output_spec * rel_join_output (int table,
* int field, ...);
*
* Construct a list of fields to be included in the output of
* a `rel_join' call.
*
* The argument list is a sequence of pairs, terminated by a single -1.
*
* Each pair names a `table' (1 or 2) and a `field' (0 based).
*/
struct rel_join_output_spec *
_rel_join_output (char const * name,
int table,
int field, ...)
{
va_list ap;
struct rel_join_output_spec * answer;
struct rel_join_output_spec * item;
int x;
static struct rel_join_output_spec ** cache = 0;
answer = 0;
for (x = 0; !answer && x < ar_size_rel_join_output_specs (cache); ++x)
{
item = cache[x];
if (item->table != table || item->field != field)
continue;
va_start (ap, field);
while (1)
{
int tmp_table;
int tmp_field;
++item;
tmp_table = va_arg (ap, int);
if (tmp_table < 0)
tmp_field = -1;
else
tmp_field = va_arg (ap, int);
if (item->table != tmp_table || item->field != tmp_field)
break;
if (item->table == -1)
{
answer = cache[x];
break;
}
}
va_end (ap);
}
if (!answer)
{
struct rel_join_output_spec item;
item.table = table;
item.field = field;
ar_push_ext_rel_join_output_spec (&answer, item, name);
va_start (ap, field);
while (1)
{
table = va_arg (ap, int);
if (table < 0)
break;
field = va_arg (ap, int);
item.table = table;
item.field = field;
ar_push_ext_rel_join_output_spec (&answer, item, name);
}
va_end (ap);
item.table = -1;
item.field = -1;
ar_push_ext_rel_join_output_spec (&answer, item, name);
ar_push_ext_rel_join_output_specs (&cache, answer, name);
}
return answer;
}
/************************************************************************
*(h1 "Reading Tables from Streams")
*
*
*
*/
/*(c rel_read_table)
* rel_table rel_read_table (int fd,
* int n_fields,
* char * err_name,
* char * err_src);
*
* Read a table with `n_fields' per row from descriptor
* `fd'. (Fields are whitespace-separated strings, rows
* are separated by newlines.)
*
* In the event of an I/O or syntax error, report an error
* from program `err_name' concerning input from `err_src'
* and exit with status 2.
*/
rel_table
_rel_read_table (int fd,
int n_fields,
char * err_name,
char * err_src,
char const * name)
{
rel_record rec;
rel_table answer;
answer = 0;
while (1)
{
rec = rel_read_record (fd, n_fields, err_name, err_src, name);
if (!rec)
break;
_rel_add_record (&answer, rec, name);
}
return answer;
}
static rel_record
rel_read_record (int fd,
int n_fields,
char * err_name,
char * err_src,
char const * name)
{
t_uchar * line;
long len;
t_uchar * pos;
int f;
rel_record answer;
safe_next_line (&line, &len, fd);
if (!line)
return 0;
answer = 0;
ar_setsize_ext_rel_record (&answer, n_fields, name);
pos = line;
for (f = 0; f < n_fields; ++f)
{
while (len && !char_is_space (*pos))
{
++pos;
--len;
}
if (!len || (pos == line))
{
if (err_name)
{
safe_printfmt (2, "%s: ill formated input\n", err_name);
safe_printfmt (2, " input source: %s\n", err_src);
exit (2);
}
}
answer[f] = talloc_strndup (ar_base(answer), line, pos - line);
while (len && char_is_space (*pos))
{
++pos;
--len;
}
line = pos;
}
return answer;
}
/*(c rel_read_pika_unescape_iso8859_1_table)
* rel_table rel_read_pika_unescape_iso8859_1_table (int fd,
* int n_fields,
* char * err_name,
* char * err_src);
*
* Read an escaped table with `n_fields' per row from descriptor
* `fd'. (Fields are whitespace-separated strings, rows
* are separated by newlines.)
*
* Escape sequences will be unescaped
*
* In the event of an I/O or syntax error, report an error
* from program `err_name' concerning input from `err_src'
* and exit with status 2.
*/
rel_table
_rel_read_pika_unescape_iso8859_1_table (int fd,
int n_fields,
char * err_name,
char * err_src,
char const * name)
{
rel_record rec;
rel_table answer;
answer = 0;
while (1)
{
rec = rel_read_pika_unescape_iso8859_1_record (fd, n_fields, err_name, err_src, name);
if (!rec)
break;
_rel_add_record (&answer, rec, name);
}
return answer;
}
static rel_record
rel_read_pika_unescape_iso8859_1_record (int fd,
int n_fields,
char * err_name,
char * err_src,
char const * name)
{
t_uchar * line;
long len;
t_uchar * pos;
int f;
rel_record answer;
safe_next_line (&line, &len, fd);
if (!line)
return 0;
answer = 0;
ar_setsize_ext_rel_record (&answer, n_fields, name);
pos = line;
for (f = 0; f < n_fields; ++f)
{
t_uchar * temp_str;
while (len && !char_is_space (*pos))
{
++pos;
--len;
}
if (!len || (pos == line))
{
if (err_name)
{
safe_printfmt (2, "%s: ill formated input\n", err_name);
safe_printfmt (2, " input source: %s\n", err_src);
exit (2);
}
}
temp_str = pika_save_unescape_iso8859_1_n (0, 0, line, pos - line);
answer[f] = talloc_strdup (ar_base (answer), temp_str);
lim_free (0, temp_str);
while (len && char_is_space (*pos))
{
++pos;
--len;
}
line = pos;
}
return answer;
}
/************************************************************************
*(h1 "Printing Tables to Streams")
*
*
*
*/
/*(c rel_print_table)
* void rel_print_table (int fd, rel_table table);
*
* Print `table' on descriptor `fd' as one record per
* line, with fields separated by tabs.
*
* In the event of an I/O error, exit with non-0, non-1 status.
*/
void
rel_print_table (int fd, rel_table table)
{
int recs;
int r;
recs = ar_size_rel_table (table);
for (r = 0; r < recs; ++r)
rel_print_record (fd, table[r]);
}
void
rel_print_pika_escape_iso8859_1_table (int fd, int escape_classes, rel_table table)
{
int recs;
int r;
recs = ar_size_rel_table (table);
for (r = 0; r < recs; ++r)
rel_print_pika_escape_iso8859_1_record (fd, escape_classes, table[r]);
}
/*(c rel_print_table_sp)
* void rel_print_table_sp (int fd, rel_table file);
*
* Print `table' on descriptor `fd' as one record per
* line, with fields separated by single spaces.
*
* In the event of an I/O error, exit with non-0, non-1 status.
*/
void
rel_print_table_sp (int fd, rel_table file)
{
int recs;
int r;
recs = ar_size_rel_table (file);
for (r = 0; r < recs; ++r)
rel_print_record_sp (fd, file[r]);
}
void
rel_print_pika_escape_iso8859_1_table_sp (int fd, int escape_classes, rel_table file)
{
int recs;
int r;
recs = ar_size_rel_table (file);
for (r = 0; r < recs; ++r)
rel_print_pika_escape_iso8859_1_record_sp (fd, escape_classes, file[r]);
}
static void
rel_print_record (int fd, rel_record rec)
{
int fields;
int f;
fields = ar_size_rel_record (rec);
for (f = 0; f < fields; ++f)
{
safe_printfmt (fd, "%s%s", (f ? "\t" : ""), rec[f]);
}
if (f)
safe_printfmt (fd, "\n");
}
static void
rel_print_pika_escape_iso8859_1_record (int fd, int escape_classes, rel_record rec)
{
int fields;
int f;
fields = ar_size_rel_record (rec);
for (f = 0; f < fields; ++f)
{
t_uchar * item;
item = pika_save_escape_iso8859_1 (0, 0, escape_classes, rec[f]);
safe_printfmt (fd, "%s%s", (f ? "\t" : ""), item);
lim_free (0, item);
}
if (f)
safe_printfmt (fd, "\n");
}
static void
rel_print_record_sp (int fd, rel_record rec)
{
int fields;
int f;
fields = ar_size_rel_record (rec);
for (f = 0; f < fields; ++f)
{
safe_printfmt (fd, "%s%s", (f ? " " : ""), rec[f]);
}
if (f)
safe_printfmt (fd, "\n");
}
static void
rel_print_pika_escape_iso8859_1_record_sp (int fd, int escape_classes, rel_record rec)
{
int fields;
int f;
fields = ar_size_rel_record (rec);
for (f = 0; f < fields; ++f)
{
t_uchar * item;
item = pika_save_escape_iso8859_1 (0, 0, escape_classes, rec[f]);
safe_printfmt (fd, "%s%s", (f ? " " : ""), item);
lim_free (0, item);
}
if (f)
safe_printfmt (fd, "\n");
}
/* rel_set_subtract:
* return a new set containing only element in left and not
* in right.
* identity is considered the first column of the rel tables
* but the entire records are copied
* left and right may be mutated
* the answer is not sorted.
*/
rel_table
rel_set_subtract (rel_table left, rel_table right)
{
rel_table answer;
rel_sort_table_by_field (0, left, 0);
rel_sort_table_by_field (0, right, 0);
answer = rel_join (1, rel_join_output (1,0, -1), 0, 0, left, right);
return answer;
}
/**
* \brief convert from a single list of strings to a reltable with
* the supplied number of elements per row.
* count % width must be 0 or a panic will be caused.
* \return rel_table
* \param count the number of records in the suppplied array
* \param width how wide the output rel table should be.
* \para, strings the strings to convert.
*/
rel_table
rel_unflatten (int count, int width, char * strings[])
{
rel_table result = NULL;
int position;
invariant (count % width == 0);
for (position = 0; position < count / width; ++position)
{
rel_record record = NULL;
int col;
for (col=0; col < width; ++col)
rel_add_field (&record, strings[position * width + col]);
rel_add_record (&result, record);
}
return result;
}
/* tag: Tom Lord Mon May 5 12:50:00 2003 (relational.c)
*/
syntax highlighted by Code2HTML, v. 0.9.1