--- /dev/null
+/* PSPP - a program for statistical analysis.
+ Copyright (C) 2016 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "data/attributes.h"
+#include "data/dataset.h"
+#include "data/dictionary.h"
+#include "data/format.h"
+#include "data/variable.h"
+#include "language/command.h"
+#include "language/lexer/lexer.h"
+#include "libpspp/array.h"
+#include "libpspp/assertion.h"
+#include "libpspp/i18n.h"
+#include "libpspp/message.h"
+#include "libpspp/str.h"
+
+#include "gl/xalloc.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+enum key
+ {
+ K_NAME,
+ K_TYPE,
+ K_FORMAT,
+ K_VAR_LABEL,
+ K_VALUE_LABELS,
+ K_MISSING_VALUES,
+ K_MEASURE,
+ K_ROLE,
+ K_COLUMNS,
+ K_ALIGNMENT,
+ K_ATTRIBUTE,
+ };
+
+struct criterion
+ {
+ enum key key;
+ char *attr_name;
+ bool descending;
+ };
+
+static int
+compare_ints (int a, int b)
+{
+ return a < b ? -1 : a > b;
+}
+
+static int
+compare_formats (const struct fmt_spec *a, const struct fmt_spec *b)
+{
+ int retval = compare_ints (fmt_to_io (a->type), fmt_to_io (b->type));
+ if (!retval)
+ retval = compare_ints (a->w, b->w);
+ if (!retval)
+ retval = compare_ints (a->d, b->d);
+ return retval;
+}
+
+static int
+compare_var_labels (const struct variable *a, const struct variable *b)
+{
+ const char *a_label = var_get_label (a);
+ const char *b_label = var_get_label (b);
+ return utf8_strcasecmp (a_label ? a_label : "",
+ b_label ? b_label : "");
+}
+
+static int
+map_measure (enum measure m)
+{
+ return (m == MEASURE_NOMINAL ? 0
+ : m == MEASURE_ORDINAL ? 1
+ : 2);
+}
+
+static int
+map_role (enum var_role r)
+{
+ return (r == ROLE_INPUT ? 0
+ : r == ROLE_TARGET ? 1
+ : r == ROLE_BOTH ? 2
+ : r == ROLE_NONE ? 3
+ : r == ROLE_PARTITION ? 4
+ : 5);
+}
+
+static const char *
+get_attribute (const struct variable *v, const char *name)
+{
+ const struct attrset *set = var_get_attributes (v);
+ const struct attribute *attr = attrset_lookup (set, name);
+ const char *value = attr ? attribute_get_value (attr, 0) : NULL;
+ return value ? value : "";
+}
+
+static int
+map_alignment (enum alignment a)
+{
+ return (a == ALIGN_LEFT ? 0
+ : a == ALIGN_RIGHT ? 1
+ : 2);
+}
+
+static int
+compare_vars (const void *a_, const void *b_, const void *c_)
+{
+ const struct variable *const *ap = a_;
+ const struct variable *const *bp = b_;
+ const struct variable *a = *ap;
+ const struct variable *b = *bp;
+ const struct criterion *c = c_;
+
+ int retval;
+ switch (c->key)
+ {
+ case K_NAME:
+ retval = utf8_strverscasecmp (var_get_name (a), var_get_name (b));
+ break;
+
+ case K_TYPE:
+ retval = compare_ints (var_get_width (a), var_get_width (b));
+ break;
+
+ case K_FORMAT:
+ retval = compare_formats (var_get_print_format (a),
+ var_get_print_format (b));
+ break;
+
+ case K_VAR_LABEL:
+ retval = compare_var_labels (a, b);
+ break;
+
+ case K_VALUE_LABELS:
+ retval = compare_ints (var_has_value_labels (a),
+ var_has_value_labels (b));
+ break;
+
+ case K_MISSING_VALUES:
+ retval = compare_ints (var_has_missing_values (a),
+ var_has_missing_values (b));
+ break;
+
+ case K_MEASURE:
+ retval = compare_ints (map_measure (var_get_measure (a)),
+ map_measure (var_get_measure (b)));
+ break;
+
+ case K_ROLE:
+ retval = compare_ints (map_role (var_get_role (a)),
+ map_role (var_get_role (b)));
+ break;
+
+ case K_COLUMNS:
+ retval = compare_ints (var_get_display_width (a),
+ var_get_display_width (b));
+ break;
+
+ case K_ALIGNMENT:
+ retval = compare_ints (map_alignment (var_get_alignment (a)),
+ map_alignment (var_get_alignment (b)));
+ break;
+
+ case K_ATTRIBUTE:
+ retval = utf8_strcasecmp (get_attribute (a, c->attr_name),
+ get_attribute (b, c->attr_name));
+ break;
+
+ default:
+ NOT_REACHED ();
+ }
+
+ /* Make this a stable sort. */
+ if (!retval)
+ {
+ size_t a_index = var_get_dict_index (a);
+ size_t b_index = var_get_dict_index (b);
+ retval = a_index < b_index ? -1 : a_index > b_index;
+ }
+
+ if (c->descending)
+ retval = -retval;
+
+ return retval;
+}
+
+/* Performs SORT VARIABLES command. */
+int
+cmd_sort_variables (struct lexer *lexer, struct dataset *ds)
+{
+ enum cmd_result result = CMD_FAILURE;
+
+ lex_match (lexer, T_BY);
+
+ /* Parse sort key. */
+ struct criterion c = { .attr_name = NULL };
+ if (lex_match_id (lexer, "NAME"))
+ c.key = K_NAME;
+ else if (lex_match_id (lexer, "TYPE"))
+ c.key = K_TYPE;
+ else if (lex_match_id (lexer, "FORMAT"))
+ c.key = K_FORMAT;
+ else if (lex_match_id (lexer, "LABEL"))
+ c.key = K_VAR_LABEL;
+ else if (lex_match_id (lexer, "VALUES"))
+ c.key = K_VALUE_LABELS;
+ else if (lex_match_id (lexer, "MISSING"))
+ c.key = K_MISSING_VALUES;
+ else if (lex_match_id (lexer, "MEASURE"))
+ c.key = K_MEASURE;
+ else if (lex_match_id (lexer, "ROLE"))
+ c.key = K_ROLE;
+ else if (lex_match_id (lexer, "COLUMNS"))
+ c.key = K_COLUMNS;
+ else if (lex_match_id (lexer, "ALIGNMENT"))
+ c.key = K_ALIGNMENT;
+ else if (lex_match_id (lexer, "ATTRIBUTE"))
+ {
+ if (!lex_force_id (lexer))
+ goto exit;
+ c.key = K_ATTRIBUTE;
+ c.attr_name = xstrdup (lex_tokcstr (lexer));
+ lex_get (lexer);
+ }
+ else
+ {
+ lex_error_expecting (lexer, "NAME", "TYPE", "FORMAT", "LABEL",
+ "VALUES", "MISSING", "MEASURE", "ROLE",
+ "COLUMNS", "ALIGNMENT", "ATTRIBUTE");
+ return CMD_FAILURE;
+ }
+
+ /* Parse sort direction. */
+ if (lex_match (lexer, T_LPAREN))
+ {
+ if (lex_match_id (lexer, "A") || lex_match_id (lexer, "UP"))
+ c.descending = false;
+ else if (lex_match_id (lexer, "D") || lex_match_id (lexer, "DOWN"))
+ c.descending = true;
+ else
+ {
+ lex_error_expecting (lexer, "A", "D");
+ goto exit;
+ }
+ if (!lex_force_match (lexer, T_RPAREN))
+ goto exit;
+ }
+ else
+ c.descending = false;
+
+ /* Sort variables. */
+ struct dictionary *d = dataset_dict (ds);
+ struct variable **vars;
+ size_t n_vars;
+ dict_get_vars_mutable (d, &vars, &n_vars, 0);
+ sort (vars, n_vars, sizeof *vars, compare_vars, &c);
+ dict_reorder_vars (d, CONST_CAST (struct variable *const *, vars), n_vars);
+ free (vars);
+
+ result = CMD_SUCCESS;
+
+exit:
+ free (c.attr_name);
+ return result;
+}