#include "data/casereader.h"
#include "data/casewriter.h"
+#include "data/data-in.h"
#include "data/data-out.h"
#include "data/dataset.h"
#include "data/dictionary.h"
#include "language/command.h"
#include "language/lexer/format-parser.h"
#include "language/lexer/lexer.h"
+#include "language/lexer/token.h"
#include "language/lexer/variable-parser.h"
#include "libpspp/array.h"
#include "libpspp/assertion.h"
/* CTPO_CAT_NUMBER. */
double number;
- /* CTPO_CAT_STRING. */
- char *string;
+ /* CTPO_CAT_STRING, in dictionary encoding. */
+ struct substring string;
/* CTPO_CAT_RANGE. */
double range[2];
/* Explicit category lists. */
CCT_NUMBER,
CCT_STRING,
- CCT_RANGE,
+ CCT_NRANGE, /* Numerical range. */
+ CCT_SRANGE, /* String range. */
CCT_MISSING,
CCT_OTHERNM,
CCT_POSTCOMPUTE,
union
{
- double number; /* CCT_NUMBER. */
- char *string; /* CCT_STRING. In dictionary encoding. */
- double range[2]; /* CCT_RANGE. */
+ double number; /* CCT_NUMBER. */
+ struct substring string; /* CCT_STRING, in dictionary encoding. */
+ double nrange[2]; /* CCT_NRANGE. */
+ struct substring srange[2]; /* CCT_SRANGE. */
struct
{
switch (cat->type)
{
case CCT_NUMBER:
- case CCT_RANGE:
+ case CCT_NRANGE:
case CCT_MISSING:
case CCT_OTHERNM:
case CCT_POSTCOMPUTE:
break;
case CCT_STRING:
- free (cat->string);
+ ss_dealloc (&cat->string);
+ break;
+
+ case CCT_SRANGE:
+ ss_dealloc (&cat->srange[0]);
+ ss_dealloc (&cat->srange[1]);
break;
case CCT_SUBTOTAL:
}
}
+static bool
+nullable_substring_equal (const struct substring *a,
+ const struct substring *b)
+{
+ return !a->string ? !b->string : b->string && ss_equals (*a, *b);
+}
+
static bool
ctables_category_equal (const struct ctables_category *a,
const struct ctables_category *b)
return a->number == b->number;
case CCT_STRING:
- return strcmp (a->string, b->string);
+ return ss_equals (a->string, b->string);
- case CCT_RANGE:
- return a->range[0] == b->range[0] && a->range[1] == b->range[1];
+ case CCT_NRANGE:
+ return a->nrange[0] == b->nrange[0] && a->nrange[1] == b->nrange[1];
+
+ case CCT_SRANGE:
+ return (nullable_substring_equal (&a->srange[0], &b->srange[0])
+ && nullable_substring_equal (&a->srange[1], &b->srange[1]));
case CCT_MISSING:
case CCT_OTHERNM:
}
static struct ctables_category
-cct_range (double low, double high)
+cct_nrange (double low, double high)
{
return (struct ctables_category) {
- .type = CCT_RANGE,
- .range = { low, high }
+ .type = CCT_NRANGE,
+ .nrange = { low, high }
+ };
+}
+
+static struct ctables_category
+cct_srange (struct substring low, struct substring high)
+{
+ return (struct ctables_category) {
+ .type = CCT_SRANGE,
+ .srange = { low, high }
};
}
return true;
}
+static struct substring
+parse_substring (struct lexer *lexer, struct dictionary *dict)
+{
+ struct substring s = recode_substring_pool (
+ dict_get_encoding (dict), "UTF-8", lex_tokss (lexer), NULL);
+ ss_rtrim (&s, ss_cstr (" "));
+ lex_get (lexer);
+ return s;
+}
+
static bool
-ctables_table_parse_explicit_category (struct lexer *lexer, struct ctables *ct,
+ctables_table_parse_explicit_category (struct lexer *lexer,
+ struct dictionary *dict,
+ struct ctables *ct,
struct ctables_category *cat)
{
if (lex_match_id (lexer, "OTHERNM"))
return ctables_table_parse_subtotal (lexer, true, cat);
else if (lex_match_id (lexer, "LO"))
{
- if (!lex_force_match_id (lexer, "THRU") || lex_force_num (lexer))
+ if (!lex_force_match_id (lexer, "THRU"))
+ return false;
+ if (lex_is_string (lexer))
+ {
+ struct substring sr0 = { .string = NULL };
+ struct substring sr1 = parse_substring (lexer, dict);
+ *cat = cct_srange (sr0, sr1);
+ }
+ else if (lex_force_num (lexer))
+ {
+ *cat = cct_nrange (-DBL_MAX, lex_number (lexer));
+ lex_get (lexer);
+ }
+ else
return false;
- *cat = cct_range (-DBL_MAX, lex_number (lexer));
- lex_get (lexer);
}
else if (lex_is_number (lexer))
{
if (lex_match_id (lexer, "THRU"))
{
if (lex_match_id (lexer, "HI"))
- *cat = cct_range (number, DBL_MAX);
+ *cat = cct_nrange (number, DBL_MAX);
else
{
if (!lex_force_num (lexer))
return false;
- *cat = cct_range (number, lex_number (lexer));
+ *cat = cct_nrange (number, lex_number (lexer));
lex_get (lexer);
}
}
}
else if (lex_is_string (lexer))
{
- *cat = (struct ctables_category) {
- .type = CCT_STRING,
- .string = ss_xstrdup (lex_tokss (lexer)),
- };
- lex_get (lexer);
+ struct substring s = parse_substring (lexer, dict);
+ if (lex_match_id (lexer, "THRU"))
+ {
+ if (lex_match_id (lexer, "HI"))
+ {
+ struct substring sr1 = { .string = NULL };
+ *cat = cct_srange (s, sr1);
+ }
+ else
+ {
+ if (!lex_force_string (lexer))
+ return false;
+ struct substring sr1 = parse_substring (lexer, dict);
+ *cat = cct_srange (s, sr1);
+ }
+ }
+ else
+ *cat = (struct ctables_category) { .type = CCT_STRING, .string = s };
}
else if (lex_match (lexer, T_AND))
{
break;
case CTPO_CAT_STRING:
- if (cat->type == CCT_STRING && !strcmp (cat->string, e->string))
+ if (cat->type == CCT_STRING && ss_equals (cat->string, e->string))
best = cat;
break;
case CTPO_CAT_RANGE:
- if (cat->type == CCT_RANGE
- && cat->range[0] == e->range[0]
- && cat->range[1] == e->range[1])
+ if (cat->type == CCT_NRANGE
+ && cat->nrange[0] == e->range[0]
+ && cat->nrange[1] == e->range[1])
best = cat;
break;
}
}
+static bool
+parse_category_string (const struct ctables_category *cat,
+ struct substring s, struct dictionary *dict,
+ enum fmt_type format, double *n)
+{
+ union value v;
+ char *error = data_in (s, dict_get_encoding (dict), format,
+ settings_get_fmt_settings (), &v, 0, NULL);
+ if (error)
+ {
+ msg_at (SE, cat->location,
+ _("Failed to parse category specification as format %s: %s."),
+ fmt_name (format), error);
+ free (error);
+ return false;
+ }
+
+ *n = v.f;
+ return true;
+}
+
+static bool
+all_strings (struct variable **vars, size_t n_vars,
+ const struct ctables_category *cat)
+{
+ for (size_t j = 0; j < n_vars; j++)
+ if (var_is_numeric (vars[j]))
+ {
+ msg_at (SE, cat->location,
+ _("This category specification may be applied only to string "
+ "variables, but this subcommand tries to apply it to "
+ "numeric variable %s."),
+ var_get_name (vars[j]));
+ return false;
+ }
+ return true;
+}
+
static bool
ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
struct ctables *ct, struct ctables_table *t)
if (!parse_variables (lexer, dict, &vars, &n_vars, PV_NO_SCRATCH))
return false;
+ const struct fmt_spec *common_format = var_get_print_format (vars[0]);
+ for (size_t i = 1; i < n_vars; i++)
+ {
+ const struct fmt_spec *f = var_get_print_format (vars[i]);
+ if (f->type != common_format->type)
+ {
+ common_format = NULL;
+ break;
+ }
+ }
+ bool parse_strings
+ = (common_format
+ && (fmt_get_category (common_format->type)
+ & (FMT_CAT_DATE | FMT_CAT_TIME | FMT_CAT_DATE_COMPONENT)));
+
struct ctables_categories *c = xmalloc (sizeof *c);
*c = (struct ctables_categories) { .n_refs = n_vars, .show_empty = true };
for (size_t i = 0; i < n_vars; i++)
ctables_categories_unref (*cp);
*cp = c;
}
- free (vars);
size_t allocated_cats = 0;
if (lex_match (lexer, T_LBRACK))
int start_ofs = lex_ofs (lexer);
struct ctables_category *cat = &c->cats[c->n_cats];
- if (!ctables_table_parse_explicit_category (lexer, ct, cat))
+ if (!ctables_table_parse_explicit_category (lexer, dict, ct, cat))
return false;
cat->location = lex_ofs_location (lexer, start_ofs, lex_ofs (lexer) - 1);
c->n_cats++;
for (size_t i = 0; i < c->n_cats; i++)
{
struct ctables_category *cat = &c->cats[i];
- if (cat->type == CCT_POSTCOMPUTE
- && !ctables_recursive_check_postcompute (cat->pc->expr, cat,
- c, cats_location))
- return false;
+ switch (cat->type)
+ {
+ case CCT_POSTCOMPUTE:
+ if (!ctables_recursive_check_postcompute (cat->pc->expr, cat,
+ c, cats_location))
+ return false;
+ break;
+
+ case CCT_NUMBER:
+ case CCT_NRANGE:
+ for (size_t j = 0; j < n_vars; j++)
+ if (var_is_alpha (vars[j]))
+ {
+ msg_at (SE, cat->location,
+ _("This category specification may be applied "
+ "only to numeric variables, but this "
+ "subcommand tries to apply it to string "
+ "variable %s."),
+ var_get_name (vars[j]));
+ return false;
+ }
+ break;
+
+ case CCT_STRING:
+ if (parse_strings)
+ {
+ double n;
+ if (!parse_category_string (cat, cat->string, dict,
+ common_format->type, &n))
+ return false;
+
+ ss_dealloc (&cat->string);
+
+ cat->type = CCT_NUMBER;
+ cat->number = n;
+ }
+ else if (!all_strings (vars, n_vars, cat))
+ return false;
+ break;
+
+ case CCT_SRANGE:
+ if (parse_strings)
+ {
+ double n[2];
+
+ if (!cat->srange[0].string)
+ n[0] = -DBL_MAX;
+ else if (!parse_category_string (cat, cat->srange[0], dict,
+ common_format->type, &n[0]))
+ return false;
+
+ if (!cat->srange[1].string)
+ n[1] = DBL_MAX;
+ else if (!parse_category_string (cat, cat->srange[1], dict,
+ common_format->type, &n[1]))
+ return false;
+
+ ss_dealloc (&cat->srange[0]);
+ ss_dealloc (&cat->srange[1]);
+
+ cat->type = CCT_NRANGE;
+ cat->nrange[0] = n[0];
+ cat->nrange[1] = n[1];
+ }
+ else if (!all_strings (vars, n_vars, cat))
+ return false;
+ break;
+
+ case CCT_MISSING:
+ case CCT_OTHERNM:
+ case CCT_SUBTOTAL:
+ case CCT_TOTAL:
+ case CCT_VALUE:
+ case CCT_LABEL:
+ case CCT_FUNCTION:
+ case CCT_EXCLUDED_MISSING:
+ break;
+ }
}
}
{
case CCT_NUMBER:
case CCT_STRING:
- case CCT_RANGE:
+ case CCT_NRANGE:
+ case CCT_SRANGE:
case CCT_MISSING:
case CCT_OTHERNM:
cat->subtotal = subtotal;
/* Must be equal. */
continue;
- case CCT_RANGE:
+ case CCT_NRANGE:
+ case CCT_SRANGE:
case CCT_MISSING:
case CCT_OTHERNM:
{
return d;
}
+static struct substring
+rtrim_value (const union value *v, const struct variable *var)
+{
+ struct substring s = ss_buffer (CHAR_CAST (char *, v->s),
+ var_get_width (var));
+ ss_rtrim (&s, ss_cstr (" "));
+ return s;
+}
+
+static bool
+in_string_range (const union value *v, const struct variable *var,
+ const struct substring *srange)
+{
+ struct substring s = rtrim_value (v, var);
+ return ((!srange[0].string || ss_compare (s, srange[0]) >= 0)
+ && (!srange[1].string || ss_compare (s, srange[1]) <= 0));
+}
+
static const struct ctables_category *
ctables_categories_match (const struct ctables_categories *c,
const union value *v, const struct variable *var)
break;
case CCT_STRING:
- NOT_REACHED ();
+ if (ss_equals (cat->string, rtrim_value (v, var)))
+ return cat;
+ break;
+
+ case CCT_NRANGE:
+ if ((cat->nrange[0] == -DBL_MAX || v->f >= cat->nrange[0])
+ && (cat->nrange[1] == DBL_MAX || v->f <= cat->nrange[1]))
+ return cat;
+ break;
- case CCT_RANGE:
- if ((cat->range[0] == -DBL_MAX || v->f >= cat->range[0])
- && (cat->range[1] == DBL_MAX || v->f <= cat->range[1]))
+ case CCT_SRANGE:
+ if (in_string_range (v, var, cat->srange))
return cat;
break;
}
static struct pivot_value *
-ctables_category_create_label (const struct ctables_category *cat,
- const struct variable *var,
- const union value *value)
+ctables_category_create_label__ (const struct ctables_category *cat,
+ const struct variable *var,
+ const union value *value)
{
return (cat->type == CCT_TOTAL || cat->type == CCT_SUBTOTAL
? pivot_value_new_user_text (cat->total_label, SIZE_MAX)
- : cat->type == CCT_POSTCOMPUTE && cat->pc->label
- ? pivot_value_new_user_text (cat->pc->label, SIZE_MAX)
: pivot_value_new_var_value (var, value));
}
+static struct pivot_value *
+ctables_postcompute_label (const struct ctables_categories *cats,
+ const struct ctables_category *cat,
+ const struct variable *var,
+ const union value *value)
+{
+ struct substring in = ss_cstr (cat->pc->label);
+ struct substring target = ss_cstr (")LABEL[");
+
+ struct string out = DS_EMPTY_INITIALIZER;
+ for (;;)
+ {
+ size_t chunk = ss_find_substring (in, target);
+ if (chunk == SIZE_MAX)
+ {
+ if (ds_is_empty (&out))
+ return pivot_value_new_user_text (in.string, in.length);
+ else
+ {
+ ds_put_substring (&out, in);
+ return pivot_value_new_user_text_nocopy (ds_steal_cstr (&out));
+ }
+ }
+
+ ds_put_substring (&out, ss_head (in, chunk));
+ ss_advance (&in, chunk + target.length);
+
+ struct substring idx_s;
+ if (!ss_get_until (&in, ']', &idx_s))
+ goto error;
+ char *tail;
+ long int idx = strtol (idx_s.string, &tail, 10);
+ if (idx < 1 || idx > cats->n_cats || tail != ss_end (idx_s))
+ goto error;
+
+ struct ctables_category *cat2 = &cats->cats[idx - 1];
+ struct pivot_value *label2
+ = ctables_category_create_label__ (cat2, var, value);
+ char *label2_s = pivot_value_to_string_defaults (label2);
+ ds_put_cstr (&out, label2_s);
+ free (label2_s);
+ pivot_value_destroy (label2);
+ }
+
+error:
+ ds_destroy (&out);
+ return pivot_value_new_user_text (cat->pc->label, SIZE_MAX);
+}
+
+static struct pivot_value *
+ctables_category_create_label (const struct ctables_categories *cats,
+ const struct ctables_category *cat,
+ const struct variable *var,
+ const union value *value)
+{
+ return (cat->type == CCT_POSTCOMPUTE && cat->pc->label
+ ? ctables_postcompute_label (cats, cat, var, value)
+ : ctables_category_create_label__ (cat, var, value));
+}
+
static struct ctables_value *
ctables_value_find__ (struct ctables_table *t, const union value *value,
int width, unsigned int hash)
pivot_table_set_caption (
pt, pivot_value_new_user_text (t->caption, SIZE_MAX));
if (t->corner)
- pivot_table_set_caption (
+ pivot_table_set_corner_text (
pt, pivot_value_new_user_text (t->corner, SIZE_MAX));
bool summary_dimension = (t->summary_axis != t->slabels_axis
const struct ctables_category *cat = ctables_categories_match (c, &value->value, var);
assert (cat != NULL);
pivot_category_create_leaf (d->root, ctables_category_create_label (
- cat, t->clabels_example, &value->value));
+ c, cat, t->clabels_example,
+ &value->value));
}
}
else if (level->type == CTL_CATEGORY)
{
const struct ctables_cell_value *cv = &cell->axes[a].cvs[level->var_idx];
- label = ctables_category_create_label (cv->category,
- var, &cv->value);
+ label = ctables_category_create_label (
+ t->categories[var_get_dict_index (var)],
+ cv->category, var, &cv->value);
}
else
NOT_REACHED ();
break;
case CCT_STRING:
- abort (); /* XXX */
+ {
+ int width = var_get_width (var);
+ union value value;
+ value_init (&value, width);
+ value_copy_buf_rpad (&value, width,
+ CHAR_CAST (uint8_t *, c->string.string),
+ c->string.length, ' ');
+ ctables_add_occurrence (var, &value, occurrences);
+ value_destroy (&value, width);
+ }
+ break;
- case CCT_RANGE:
+ case CCT_NRANGE:
assert (var_is_numeric (var));
for (const struct val_lab *vl = val_labs_first (val_labs); vl;
vl = val_labs_next (val_labs, vl))
- if (vl->value.f >= c->range[0] && vl->value.f <= c->range[1])
+ if (vl->value.f >= c->nrange[0] && vl->value.f <= c->nrange[1])
+ ctables_add_occurrence (var, &vl->value, occurrences);
+ break;
+
+ case CCT_SRANGE:
+ assert (var_is_alpha (var));
+ for (const struct val_lab *vl = val_labs_first (val_labs); vl;
+ vl = val_labs_next (val_labs, vl))
+ if (in_string_range (&vl->value, var, c->srange))
ctables_add_occurrence (var, &vl->value, occurrences);
break;
\f
/* Postcomputes. */
-typedef struct ctables_pcexpr *parse_recursively_func (struct lexer *);
+typedef struct ctables_pcexpr *parse_recursively_func (struct lexer *,
+ struct dictionary *);
static void
ctables_pcexpr_destroy (struct ctables_pcexpr *e)
switch (e->op)
{
case CTPO_CAT_STRING:
- free (e->string);
+ ss_dealloc (&e->string);
break;
case CTPO_ADD:
}
static struct ctables_pcexpr *
-parse_binary_operators__ (struct lexer *lexer,
+parse_binary_operators__ (struct lexer *lexer, struct dictionary *dict,
const struct operator ops[], size_t n_ops,
parse_recursively_func *parse_next_level,
const char *chain_warning,
return lhs;
}
- struct ctables_pcexpr *rhs = parse_next_level (lexer);
+ struct ctables_pcexpr *rhs = parse_next_level (lexer, dict);
if (!rhs)
{
ctables_pcexpr_destroy (lhs);
}
static struct ctables_pcexpr *
-parse_binary_operators (struct lexer *lexer,
+parse_binary_operators (struct lexer *lexer, struct dictionary *dict,
const struct operator ops[], size_t n_ops,
parse_recursively_func *parse_next_level,
const char *chain_warning)
{
- struct ctables_pcexpr *lhs = parse_next_level (lexer);
+ struct ctables_pcexpr *lhs = parse_next_level (lexer, dict);
if (!lhs)
return NULL;
- return parse_binary_operators__ (lexer, ops, n_ops, parse_next_level,
+ return parse_binary_operators__ (lexer, dict, ops, n_ops, parse_next_level,
chain_warning, lhs);
}
-static struct ctables_pcexpr *parse_add (struct lexer *);
+static struct ctables_pcexpr *parse_add (struct lexer *, struct dictionary *);
static struct ctables_pcexpr
ctpo_cat_range (double low, double high)
}
static struct ctables_pcexpr *
-parse_primary (struct lexer *lexer)
+parse_primary (struct lexer *lexer, struct dictionary *dict)
{
int start_ofs = lex_ofs (lexer);
struct ctables_pcexpr e;
}
else if (lex_is_string (lexer))
{
- e = (struct ctables_pcexpr) {
- .op = CTPO_CAT_STRING,
- .string = ss_xstrdup (lex_tokss (lexer)),
- };
+ struct substring s = recode_substring_pool (
+ dict_get_encoding (dict), "UTF-8", lex_tokss (lexer), NULL);
+ ss_rtrim (&s, ss_cstr (" "));
+
+ e = (struct ctables_pcexpr) { .op = CTPO_CAT_STRING, .string = s };
lex_get (lexer);
}
else
if (!lex_force_match (lexer, T_RBRACK))
{
if (e.op == CTPO_CAT_STRING)
- free (e.string);
+ ss_dealloc (&e.string);
return NULL;
}
}
else if (lex_match (lexer, T_LPAREN))
{
- struct ctables_pcexpr *ep = parse_add (lexer);
+ struct ctables_pcexpr *ep = parse_add (lexer, dict);
if (!ep)
return NULL;
if (!lex_force_match (lexer, T_RPAREN))
}
static struct ctables_pcexpr *
-parse_exp (struct lexer *lexer)
+parse_exp (struct lexer *lexer, struct dictionary *dict)
{
static const struct operator op = { T_EXP, CTPO_POW };
"To disable this warning, insert parentheses.");
if (lex_token (lexer) != T_NEG_NUM || lex_next_token (lexer, 1) != T_EXP)
- return parse_binary_operators (lexer, &op, 1,
+ return parse_binary_operators (lexer, dict, &op, 1,
parse_primary, chain_warning);
/* Special case for situations like "-5**6", which must be parsed as
lex_get (lexer);
struct ctables_pcexpr *node = parse_binary_operators__ (
- lexer, &op, 1, parse_primary, chain_warning, lhs);
+ lexer, dict, &op, 1, parse_primary, chain_warning, lhs);
if (!node)
return NULL;
/* Parses the unary minus level. */
static struct ctables_pcexpr *
-parse_neg (struct lexer *lexer)
+parse_neg (struct lexer *lexer, struct dictionary *dict)
{
int start_ofs = lex_ofs (lexer);
if (!lex_match (lexer, T_DASH))
- return parse_exp (lexer);
+ return parse_exp (lexer, dict);
- struct ctables_pcexpr *inner = parse_neg (lexer);
+ struct ctables_pcexpr *inner = parse_neg (lexer, dict);
if (!inner)
return NULL;
/* Parses the multiplication and division level. */
static struct ctables_pcexpr *
-parse_mul (struct lexer *lexer)
+parse_mul (struct lexer *lexer, struct dictionary *dict)
{
static const struct operator ops[] =
{
{ T_SLASH, CTPO_DIV },
};
- return parse_binary_operators (lexer, ops, sizeof ops / sizeof *ops,
+ return parse_binary_operators (lexer, dict, ops, sizeof ops / sizeof *ops,
parse_neg, NULL);
}
/* Parses the addition and subtraction level. */
static struct ctables_pcexpr *
-parse_add (struct lexer *lexer)
+parse_add (struct lexer *lexer, struct dictionary *dict)
{
static const struct operator ops[] =
{
{ T_NEG_NUM, CTPO_ADD },
};
- return parse_binary_operators (lexer, ops, sizeof ops / sizeof *ops,
+ return parse_binary_operators (lexer, dict, ops, sizeof ops / sizeof *ops,
parse_mul, NULL);
}
}
static bool
-ctables_parse_pcompute (struct lexer *lexer, struct ctables *ct)
+ctables_parse_pcompute (struct lexer *lexer, struct dictionary *dict,
+ struct ctables *ct)
{
int pcompute_start = lex_ofs (lexer) - 1;
}
int expr_start = lex_ofs (lexer);
- struct ctables_pcexpr *expr = parse_add (lexer);
+ struct ctables_pcexpr *expr = parse_add (lexer, dict);
int expr_end = lex_ofs (lexer) - 1;
if (!expr || !lex_force_match (lexer, T_RPAREN))
{
return false;
}
+static void
+put_strftime (struct string *out, time_t now, const char *format)
+{
+ const struct tm *tm = localtime (&now);
+ char value[128];
+ strftime (value, sizeof value, format, tm);
+ ds_put_cstr (out, value);
+}
+
+static bool
+skip_prefix (struct substring *s, struct substring prefix)
+{
+ if (ss_starts_with (*s, prefix))
+ {
+ ss_advance (s, prefix.length);
+ return true;
+ }
+ else
+ return false;
+}
+
+static void
+put_table_expression (struct string *out, struct lexer *lexer,
+ struct dictionary *dict, int expr_start, int expr_end)
+{
+ size_t nest = 0;
+ for (int ofs = expr_start; ofs < expr_end; ofs++)
+ {
+ const struct token *t = lex_ofs_token (lexer, ofs);
+ if (t->type == T_LBRACK)
+ nest++;
+ else if (t->type == T_RBRACK && nest > 0)
+ nest--;
+ else if (nest > 0)
+ {
+ /* Nothing. */
+ }
+ else if (t->type == T_ID)
+ {
+ const struct variable *var
+ = dict_lookup_var (dict, t->string.string);
+ const char *label = var ? var_get_label (var) : NULL;
+ ds_put_cstr (out, label ? label : t->string.string);
+ }
+ else
+ {
+ if (ofs != expr_start && t->type != T_RPAREN && ds_last (out) != ' ')
+ ds_put_byte (out, ' ');
+
+ char *repr = lex_ofs_representation (lexer, ofs, ofs);
+ ds_put_cstr (out, repr);
+ free (repr);
+
+ if (ofs + 1 != expr_end && t->type != T_LPAREN)
+ ds_put_byte (out, ' ');
+ }
+ }
+}
+
+static void
+put_title_text (struct string *out, struct substring in, time_t now,
+ struct lexer *lexer, struct dictionary *dict,
+ int expr_start, int expr_end)
+{
+ for (;;)
+ {
+ size_t chunk = ss_find_byte (in, ')');
+ ds_put_substring (out, ss_head (in, chunk));
+ ss_advance (&in, chunk);
+ if (ss_is_empty (in))
+ return;
+
+ if (skip_prefix (&in, ss_cstr (")DATE")))
+ put_strftime (out, now, "%x");
+ else if (skip_prefix (&in, ss_cstr (")TIME")))
+ put_strftime (out, now, "%X");
+ else if (skip_prefix (&in, ss_cstr (")TABLE")))
+ put_table_expression (out, lexer, dict, expr_start, expr_end);
+ else
+ {
+ ds_put_byte (out, ')');
+ ss_advance (&in, 1);
+ }
+ }
+}
+
int
cmd_ctables (struct lexer *lexer, struct dataset *ds)
{
.postcomputes = HMAP_INITIALIZER (ct->postcomputes),
};
+ time_t now = time (NULL);
+
struct ctf
{
enum fmt_type type;
}
else if (lex_match_id (lexer, "PCOMPUTE"))
{
- if (!ctables_parse_pcompute (lexer, ct))
+ if (!ctables_parse_pcompute (lexer, dataset_dict (ds), ct))
goto error;
}
else if (lex_match_id (lexer, "PPROPERTIES"))
ct->tables[ct->n_tables++] = t;
lex_match (lexer, T_EQUALS);
+ int expr_start = lex_ofs (lexer);
if (!ctables_axis_parse (lexer, dataset_dict (ds), ct, t, PIVOT_AXIS_ROW))
goto error;
if (lex_match (lexer, T_BY))
goto error;
}
}
+ int expr_end = lex_ofs (lexer);
if (!t->axes[PIVOT_AXIS_ROW] && !t->axes[PIVOT_AXIS_COLUMN]
&& !t->axes[PIVOT_AXIS_LAYER])
{
if (!ds_is_empty (&s))
ds_put_byte (&s, ' ');
- ds_put_substring (&s, lex_tokss (lexer));
+ put_title_text (&s, lex_tokss (lexer), now,
+ lexer, dataset_dict (ds),
+ expr_start, expr_end);
lex_get (lexer);
}
free (*textp);