+
+ if (ws && !fmt->sloppy)
+ new_field(n);
+ return 1;
+}
+
+/*** Regex back-end ***/
+
+static const char *regex_set(struct format *f, char *rx)
+{
+ const char *err;
+ int errpos;
+ f->pcre = pcre_compile(rx, PCRE_DOLLAR_ENDONLY, &err, &errpos, NULL);
+ if (!f->pcre)
+ return err;
+
+ f->pcre_extra = pcre_study(f->pcre, 0, &err);
+ if (!f->pcre_extra)
+ return err;
+
+ return NULL;
+}
+
+static int regex_read(struct format *fmt)
+{
+ if (!next_line())
+ return 0;
+
+ unsigned char *c = line_first(&in_line);
+ int n = line_count(&in_line);
+ if (!n)
+ return 1;
+
+ int i = 0;
+ for (;;) {
+ int ovec[3];
+ int err = pcre_exec(fmt->pcre, fmt->pcre_extra, (char *) c, n, i, 0, ovec, 3);
+ if (err < 0) {
+ if (err != PCRE_ERROR_NOMATCH)
+ warn(fmt, "PCRE matching error %d", err);
+ // No further occurrence of the separator: the rest is a single field
+ if (!fmt->sloppy || i < n) {
+ new_field(i);
+ in_field->len = n - i;
+ }
+ return 1;
+ }
+ if (ovec[0] == ovec[1]) {
+ warn(fmt, "Regular expression matched an empty separator.");
+ new_field(i);
+ in_field->len = n - i;
+ return 1;
+ }
+ if (!fmt->sloppy || ovec[0]) {
+ new_field(i);
+ in_field->len = ovec[0] - i;
+ }
+ i = ovec[1];
+ }
+}
+
+/*** Table back-end ***/
+
+static void table_write(struct format *fmt)
+{
+ for (int i = 0; i < intarray_count(&column_widths); i++) {
+ if (fmt->table_grid) {
+ putchar_unlocked('|');
+ printf("%*s", fmt->table_sep / 2, "");
+ } else if (i)
+ printf("%*s", fmt->table_sep, "");
+
+ int cw = *intarray_nth(&column_widths, i);
+ int fw = 0;
+ if (i < fields_count(&out_fields)) {
+ int len;
+ unsigned char *p = get_field(&out_fields, i, &len);
+ fw = field_chars(fields_nth(&out_fields, i));
+ if (fw > cw) {
+ warn(fmt, "Internal error: Wrongly calculated width of column %d (%d > %d)", i, fw, cw);
+ cw = fw;
+ }
+ while (len--)
+ putchar_unlocked(*p++);
+ }
+ while (fw < cw) {
+ putchar_unlocked(' ');
+ fw++;
+ }
+
+ if (fmt->table_grid)
+ printf("%*s", fmt->table_sep - fmt->table_sep / 2, "");
+ }
+
+ if (fmt->table_grid)
+ putchar_unlocked('|');
+ putchar_unlocked('\n');
+}
+
+static void table_write_grid(struct format *fmt, int pos UNUSED)
+{
+ if (!fmt->table_grid)
+ return;
+
+ for (int i = 0; i < intarray_count(&column_widths); i++) {
+ putchar_unlocked('+');
+ int w = fmt->table_sep + *intarray_nth(&column_widths, i);
+ while (w--)
+ putchar_unlocked('-');
+ }
+ putchar_unlocked('+');
+ putchar_unlocked('\n');
+}
+
+/*** Temporary file back-end ***/
+
+static int tmp_read(struct format *fmt)
+{
+ FILE *tf = fmt->tmp_file;
+
+ for (;;) {
+ int c = getc_unlocked(tf);
+ if (c < 0)
+ return 0;
+ if (c == 0xff)
+ return 1;
+ if (c == 0xfe) {
+ c = getc_unlocked(tf);
+ c = (c << 8) | getc_unlocked(tf);
+ c = (c << 8) | getc_unlocked(tf);
+ c = (c << 8) | getc_unlocked(tf);
+ }
+ new_field(line_count(&in_line));
+ in_field->len = c;
+ while (c--) {
+ int x = getc_unlocked(tf);
+ if (x < 0)
+ die("Truncated temporary file");
+ *line_push(&in_line) = x;
+ }
+ }
+
+ if (ferror_unlocked(tf))
+ die("I/O error when reading temporary file");
+}
+
+static void tmp_write(struct format *fmt)
+{
+ FILE *tf = fmt->tmp_file;
+
+ for (int i = 0; i < fields_count(&out_fields); i++) {
+ int len;
+ unsigned char *p = get_field(&out_fields, i, &len);
+
+ if (len < 0xfe)
+ putc_unlocked(len, tf);
+ else {
+ putc_unlocked(0xfe, tf);
+ putc_unlocked((len >> 24) & 0xff, tf);
+ putc_unlocked((len >> 16) & 0xff, tf);
+ putc_unlocked((len >> 8) & 0xff, tf);
+ putc_unlocked(len & 0xff, tf);
+ }
+
+ while (len--)
+ putc_unlocked(*p++, tf);
+ }
+ putc_unlocked(0xff, tf);
+
+ if (ferror_unlocked(tf))
+ die("I/O error when writing temporary file");
+}
+
+/*** Transforms ***/
+
+static void trim_fields(void)
+{
+ unsigned char *line = line_first(&in_line);
+ for (int i = 0; i < fields_count(&in_fields); i++) {
+ struct field *f = fields_nth(&in_fields, i);
+ while (f->len && is_ws(line[f->start_pos]))
+ f->start_pos++, f->len--;
+ while (f->len && is_ws(line[f->start_pos + f->len - 1]))
+ f->len--;
+ }
+}
+
+static void equalize_fields(void)
+{
+ while (fields_count(&out_fields) < intarray_count(&column_widths)) {
+ struct field *f = fields_push(&out_fields);
+ f->start_pos = f->len = 0;
+ }
+}
+
+/*** Field names and headers ***/
+
+struct field_names {
+ stringarray_t names;
+};
+
+static void add_field(struct field_names *fn, char *name, int namelen)
+{
+ char *n = xmalloc(namelen + 1);
+ memcpy(n, name, namelen);
+ n[namelen] = 0;
+ *stringarray_push(&fn->names) = n;
+}
+
+static void add_field_names(struct field_names *fn, char *names)
+{
+ char *p = names;
+ while (p) {
+ char *q = strchr(p, ',');
+ int len = q ? q-p : (int) strlen(p);
+ add_field(fn, p, len);
+ p = q ? q+1 : NULL;
+ }
+}
+
+static void read_header(void)
+{
+ if (!(in_format->has_header || in_format->set_field_names))
+ return;
+
+ struct field_names *fn = xmalloc_zero(sizeof(*fn));
+ in_format->field_names = fn;
+
+ if (in_format->has_header) {
+ if (!read_line())
+ die("Missing input header");
+ }
+
+ if (in_format->set_field_names) {
+ add_field_names(fn, in_format->set_field_names);
+ } else {
+ for (int i = 0; i < fields_count(&in_fields); i++) {
+ int len;
+ char *s = (char *) get_field(&in_fields, i, &len);
+ add_field(fn, s, len);
+ }
+ }
+}
+
+static void write_header(void)
+{
+ if (!out_format->has_header) {
+ write_grid(-1);
+ return;
+ }
+
+ int want_select_fields = 0;
+ if (out_format->set_field_names) {
+ struct field_names *fn = xmalloc_zero(sizeof(*fn));
+ out_format->field_names = fn;
+ add_field_names(fn, out_format->set_field_names);
+ } else if (in_format->field_names) {
+ out_format->field_names = in_format->field_names;
+ want_select_fields = 1;
+ } else
+ die("Output header requested, but no field names specified");
+
+ line_reset(&in_line);
+ fields_reset(&in_fields);
+ struct field_names *fn = out_format->field_names;
+ for (int i = 0; i < stringarray_count(&fn->names); i++) {
+ struct field *f = fields_push(&in_fields);
+ f->start_pos = line_count(&in_line);
+ f->len = 0;
+ char *s = *stringarray_nth(&fn->names, i);
+ while (*s) {
+ *line_push(&in_line) = *s++;
+ f->len++;
+ }
+ }
+
+ fields_reset(&out_fields);
+ if (want_select_fields)
+ select_fields();
+ else
+ select_all_fields();
+
+ // This is tricky: when we are formatting a table, field names are normally
+ // calculated in pass 1, but the header is written in pass 2, so we have to
+ // update column statistics, because field name can be too wide to fit.
+ want_stats++;
+ update_stats();
+ want_stats--;
+ if (want_equalize)
+ equalize_fields();
+ write_grid(-1);
+ write_line();
+ write_grid(0);
+}
+
+static void write_footer(void)
+{
+ write_grid(1);
+}
+
+static int find_field_by_name(struct field_names *fn, char *name)
+{
+ for (int i = 0; i < stringarray_count(&fn->names); i++)
+ if (!strcmp(*stringarray_nth(&fn->names, i), name))
+ return i + 1;
+ return -1;