lexer: Reimplement for better testability and internationalization.
[pspp-builds.git] / src / language / stats / aggregate.c
index 22ca39d76373c7bd1634e1d5ad92ef1c0f96ecf8..2a4eda87ad042092f81d3eccd93c4bb46c2f4025 100644 (file)
@@ -1,56 +1,60 @@
-/* PSPP - computes sample statistics.
-   Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 1997-9, 2000, 2006, 2008, 2009, 2010, 2011 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 2 of the
-   License, or (at your option) any later version.
+   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.
+   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, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301, USA. */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
 
+#include "language/stats/aggregate.h"
+
 #include <stdlib.h>
 
-#include <data/any-writer.h>
-#include <data/case-ordering.h>
-#include <data/case.h>
-#include <data/casegrouper.h>
-#include <data/casereader.h>
-#include <data/casewriter.h>
-#include <data/dictionary.h>
-#include <data/file-handle-def.h>
-#include <data/format.h>
-#include <data/procedure.h>
-#include <data/settings.h>
-#include <data/sys-file-writer.h>
-#include <data/variable.h>
-#include <language/command.h>
-#include <language/data-io/file-handle.h>
-#include <language/lexer/lexer.h>
-#include <language/lexer/variable-parser.h>
-#include <language/stats/sort-criteria.h>
-#include <libpspp/alloc.h>
-#include <libpspp/assertion.h>
-#include <libpspp/message.h>
-#include <libpspp/misc.h>
-#include <libpspp/pool.h>
-#include <libpspp/str.h>
-#include <math/moments.h>
-#include <math/sort.h>
-
-#include "minmax.h"
+#include "data/any-writer.h"
+#include "data/case.h"
+#include "data/casegrouper.h"
+#include "data/casereader.h"
+#include "data/casewriter.h"
+#include "data/dictionary.h"
+#include "data/file-handle-def.h"
+#include "data/format.h"
+#include "data/procedure.h"
+#include "data/settings.h"
+#include "data/subcase.h"
+#include "data/sys-file-writer.h"
+#include "data/variable.h"
+#include "language/command.h"
+#include "language/data-io/file-handle.h"
+#include "language/lexer/lexer.h"
+#include "language/lexer/variable-parser.h"
+#include "language/stats/sort-criteria.h"
+#include "libpspp/assertion.h"
+#include "libpspp/i18n.h"
+#include "libpspp/message.h"
+#include "libpspp/misc.h"
+#include "libpspp/pool.h"
+#include "libpspp/str.h"
+#include "math/moments.h"
+#include "math/percentiles.h"
+#include "math/sort.h"
+#include "math/statistic.h"
+
+#include "gl/minmax.h"
+#include "gl/xalloc.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
 
 /* Argument for AGGREGATE function. */
 union agr_argument
@@ -77,53 +81,38 @@ struct agr_var
     char *string;
     bool saw_missing;
     struct moments1 *moments;
-  };
+    double cc;
 
-/* Aggregation functions. */
-enum
-  {
-    NONE, SUM, MEAN, SD, MAX, MIN, PGT, PLT, PIN, POUT, FGT, FLT, FIN,
-    FOUT, N, NU, NMISS, NUMISS, FIRST, LAST,
-    N_AGR_FUNCS, N_NO_VARS, NU_NO_VARS,
-    FUNC = 0x1f, /* Function mask. */
-    FSTRING = 1<<5, /* String function bit. */
+    struct variable *subject;
+    struct variable *weight;
+    struct casewriter *writer;
   };
 
-/* Attributes of an aggregation function. */
-struct agr_func
-  {
-    const char *name;          /* Aggregation function name. */
-    size_t n_args;              /* Number of arguments. */
-    enum var_type alpha_type;   /* When given ALPHA arguments, output type. */
-    struct fmt_spec format;    /* Format spec if alpha_type != ALPHA. */
-  };
 
 /* Attributes of aggregation functions. */
-static const struct agr_func agr_func_tab[] = 
+const struct agr_func agr_func_tab[] =
   {
-    {"<NONE>",  0, -1,          {0, 0, 0}},
-    {"SUM",     0, -1,          {FMT_F, 8, 2}},
-    {"MEAN",   0, -1,          {FMT_F, 8, 2}},
-    {"SD",      0, -1,          {FMT_F, 8, 2}},
-    {"MAX",     0, VAR_STRING,  {-1, -1, -1}}, 
-    {"MIN",     0, VAR_STRING,  {-1, -1, -1}}, 
-    {"PGT",     1, VAR_NUMERIC, {FMT_F, 5, 1}},      
-    {"PLT",     1, VAR_NUMERIC, {FMT_F, 5, 1}},       
-    {"PIN",     2, VAR_NUMERIC, {FMT_F, 5, 1}},       
-    {"POUT",    2, VAR_NUMERIC, {FMT_F, 5, 1}},       
-    {"FGT",     1, VAR_NUMERIC, {FMT_F, 5, 3}},       
-    {"FLT",     1, VAR_NUMERIC, {FMT_F, 5, 3}},       
-    {"FIN",     2, VAR_NUMERIC, {FMT_F, 5, 3}},       
-    {"FOUT",    2, VAR_NUMERIC, {FMT_F, 5, 3}},       
-    {"N",       0, VAR_NUMERIC, {FMT_F, 7, 0}},       
-    {"NU",      0, VAR_NUMERIC, {FMT_F, 7, 0}},       
-    {"NMISS",   0, VAR_NUMERIC, {FMT_F, 7, 0}},       
-    {"NUMISS",  0, VAR_NUMERIC, {FMT_F, 7, 0}},       
-    {"FIRST",   0, VAR_STRING,  {-1, -1, -1}}, 
-    {"LAST",    0, VAR_STRING,  {-1, -1, -1}},
-    {NULL,      0, -1,          {-1, -1, -1}},
-    {"N",       0, VAR_NUMERIC, {FMT_F, 7, 0}},
-    {"NU",      0, VAR_NUMERIC, {FMT_F, 7, 0}},
+    {"SUM",     N_("Sum of values"),                         AGR_SV_YES, 0, -1,          {FMT_F, 8, 2}},
+    {"MEAN",   N_("Mean average"),                          AGR_SV_YES, 0, -1,          {FMT_F, 8, 2}},
+    {"MEDIAN", N_("Median average"),                        AGR_SV_YES, 0, -1,          {FMT_F, 8, 2}},
+    {"SD",      N_("Standard deviation"),                    AGR_SV_YES, 0, -1,          {FMT_F, 8, 2}},
+    {"MAX",     N_("Maximum value"),                         AGR_SV_YES, 0, VAL_STRING,  {-1, -1, -1}},
+    {"MIN",     N_("Minimum value"),                         AGR_SV_YES, 0, VAL_STRING,  {-1, -1, -1}},
+    {"PGT",     N_("Percentage greater than"),               AGR_SV_YES, 1, VAL_NUMERIC, {FMT_F, 5, 1}},
+    {"PLT",     N_("Percentage less than"),                  AGR_SV_YES, 1, VAL_NUMERIC, {FMT_F, 5, 1}},
+    {"PIN",     N_("Percentage included in range"),          AGR_SV_YES, 2, VAL_NUMERIC, {FMT_F, 5, 1}},
+    {"POUT",    N_("Percentage excluded from range"),        AGR_SV_YES, 2, VAL_NUMERIC, {FMT_F, 5, 1}},
+    {"FGT",     N_("Fraction greater than"),                 AGR_SV_YES, 1, VAL_NUMERIC, {FMT_F, 5, 3}},
+    {"FLT",     N_("Fraction less than"),                    AGR_SV_YES, 1, VAL_NUMERIC, {FMT_F, 5, 3}},
+    {"FIN",     N_("Fraction included in range"),            AGR_SV_YES, 2, VAL_NUMERIC, {FMT_F, 5, 3}},
+    {"FOUT",    N_("Fraction excluded from range"),          AGR_SV_YES, 2, VAL_NUMERIC, {FMT_F, 5, 3}},
+    {"N",       N_("Number of cases"),                       AGR_SV_NO,  0, VAL_NUMERIC, {FMT_F, 7, 0}},
+    {"NU",      N_("Number of cases (unweighted)"),          AGR_SV_OPT, 0, VAL_NUMERIC, {FMT_F, 7, 0}},
+    {"NMISS",   N_("Number of missing values"),              AGR_SV_YES, 0, VAL_NUMERIC, {FMT_F, 7, 0}},
+    {"NUMISS",  N_("Number of missing values (unweighted)"), AGR_SV_YES, 0, VAL_NUMERIC, {FMT_F, 7, 0}},
+    {"FIRST",   N_("First non-missing value"),               AGR_SV_YES, 0, VAL_STRING,  {-1, -1, -1}},
+    {"LAST",    N_("Last non-missing value"),                AGR_SV_YES, 0, VAL_STRING,  {-1, -1, -1}},
+    {NULL,      NULL,                                        AGR_SV_NO,  0, -1,          {-1, -1, -1}},
   };
 
 /* Missing value types. */
@@ -134,31 +123,34 @@ enum missing_treatment
   };
 
 /* An entire AGGREGATE procedure. */
-struct agr_proc 
+struct agr_proc
   {
     /* Break variables. */
-    struct case_ordering *sort;         /* Sort criteria. */
+    struct subcase sort;                /* Sort criteria (break variables). */
     const struct variable **break_vars;       /* Break variables. */
     size_t break_var_cnt;               /* Number of break variables. */
-    struct ccase break_case;            /* Last values of break variables. */
 
     enum missing_treatment missing;     /* How to treat missing values. */
     struct agr_var *agr_vars;           /* First aggregate variable. */
     struct dictionary *dict;            /* Aggregate dictionary. */
     const struct dictionary *src_dict;  /* Dict of the source */
     int case_cnt;                       /* Counts aggregated cases. */
+
+    bool add_variables;                 /* True iff the aggregated variables should
+                                          be appended to the existing dictionary */
   };
 
-static void initialize_aggregate_info (struct agr_proc *,
-                                       const struct ccase *);
+static void initialize_aggregate_info (struct agr_proc *);
+
 static void accumulate_aggregate_info (struct agr_proc *,
                                        const struct ccase *);
 /* Prototypes. */
 static bool parse_aggregate_functions (struct lexer *, const struct dictionary *,
                                       struct agr_proc *);
 static void agr_destroy (struct agr_proc *);
-static void dump_aggregate_info (struct agr_proc *agr,
-                                 struct casewriter *output);
+static void dump_aggregate_info (const struct agr_proc *agr,
+                                 struct casewriter *output,
+                                const struct ccase *break_case);
 \f
 /* Parsing. */
 
@@ -180,35 +172,58 @@ cmd_aggregate (struct lexer *lexer, struct dataset *ds)
 
   memset(&agr, 0 , sizeof (agr));
   agr.missing = ITEMWISE;
-  case_nullify (&agr.break_case);
-
-  agr.dict = dict_create ();
   agr.src_dict = dict;
-  dict_set_label (agr.dict, dict_get_label (dict));
-  dict_set_documents (agr.dict, dict_get_documents (dict));
+  subcase_init_empty (&agr.sort);
 
   /* OUTFILE subcommand must be first. */
+  lex_match (lexer, T_SLASH);
   if (!lex_force_match_id (lexer, "OUTFILE"))
     goto error;
-  lex_match (lexer, '=');
-  if (!lex_match (lexer, '*'))
+  lex_match (lexer, T_EQUALS);
+  if (!lex_match (lexer, T_ASTERISK))
     {
       out_file = fh_parse (lexer, FH_REF_FILE | FH_REF_SCRATCH);
       if (out_file == NULL)
         goto error;
     }
-  
+
+  if (out_file == NULL && lex_match_id (lexer, "MODE"))
+    {
+      lex_match (lexer, T_EQUALS);
+      if (lex_match_id (lexer, "ADDVARIABLES"))
+       {
+         agr.add_variables = true;
+
+         /* presorted is assumed in ADDVARIABLES mode */
+         presorted = true;
+       }
+      else if (lex_match_id (lexer, "REPLACE"))
+       {
+         agr.add_variables = false;
+       }
+      else
+       goto error;
+    }
+
+  if ( agr.add_variables )
+    agr.dict = dict_clone (dict);
+  else
+    agr.dict = dict_create ();    
+
+  dict_set_label (agr.dict, dict_get_label (dict));
+  dict_set_documents (agr.dict, dict_get_documents (dict));
+
   /* Read most of the subcommands. */
   for (;;)
     {
-      lex_match (lexer, '/');
-      
+      lex_match (lexer, T_SLASH);
+
       if (lex_match_id (lexer, "MISSING"))
        {
-         lex_match (lexer, '=');
+         lex_match (lexer, T_EQUALS);
          if (!lex_match_id (lexer, "COLUMNWISE"))
            {
-             lex_error (lexer, _("while expecting COLUMNWISE"));
+             lex_error (lexer, _("expecting %s"), "COLUMNWISE");
               goto error;
            }
          agr.missing = COLUMNWISE;
@@ -217,39 +232,34 @@ cmd_aggregate (struct lexer *lexer, struct dataset *ds)
         copy_documents = true;
       else if (lex_match_id (lexer, "PRESORTED"))
         presorted = true;
-      else if (lex_match_id (lexer, "BREAK"))
+      else if (lex_force_match_id (lexer, "BREAK"))
        {
           int i;
 
-         lex_match (lexer, '=');
-          agr.sort = parse_case_ordering (lexer, dict,
-                                          
-                                          &saw_direction);
-          if (agr.sort == NULL)
+         lex_match (lexer, T_EQUALS);
+          if (!parse_sort_criteria (lexer, dict, &agr.sort, &agr.break_vars,
+                                    &saw_direction))
             goto error;
-          case_ordering_get_vars (agr.sort,
-                                  &agr.break_vars, &agr.break_var_cnt);
-         
-          for (i = 0; i < agr.break_var_cnt; i++)
-            dict_clone_var_assert (agr.dict, agr.break_vars[i],
-                                   var_get_name (agr.break_vars[i]));
+          agr.break_var_cnt = subcase_get_n_fields (&agr.sort);
+
+         if  (! agr.add_variables)
+           for (i = 0; i < agr.break_var_cnt; i++)
+             dict_clone_var_assert (agr.dict, agr.break_vars[i]);
 
           /* BREAK must follow the options. */
           break;
        }
       else
-        {
-          lex_error (lexer, _("expecting BREAK"));
-          goto error;
-        }
+        goto error;
+
     }
   if (presorted && saw_direction)
     msg (SW, _("When PRESORTED is specified, specifying sorting directions "
                "with (A) or (D) has no effect.  Output data will be sorted "
                "the same way as the input data."));
-      
+
   /* Read in the aggregate functions. */
-  lex_match (lexer, '/');
+  lex_match (lexer, T_SLASH);
   if (!parse_aggregate_functions (lexer, dict, &agr))
     goto error;
 
@@ -259,19 +269,19 @@ cmd_aggregate (struct lexer *lexer, struct dataset *ds)
 
   /* Cancel SPLIT FILE. */
   dict_set_split_vars (agr.dict, NULL, 0);
-  
+
   /* Initialize. */
   agr.case_cnt = 0;
 
-  if (out_file == NULL) 
+  if (out_file == NULL)
     {
       /* The active file will be replaced by the aggregated data,
          so TEMPORARY is moot. */
       proc_cancel_temporary_transformations (ds);
       proc_discard_output (ds);
-      output = autopaging_writer_create (dict_get_next_value_idx (agr.dict));
+      output = autopaging_writer_create (dict_get_proto (agr.dict));
     }
-  else 
+  else
     {
       output = any_writer_open (out_file, agr.dict);
       if (output == NULL)
@@ -279,56 +289,81 @@ cmd_aggregate (struct lexer *lexer, struct dataset *ds)
     }
 
   input = proc_open (ds);
-  if (agr.sort != NULL && !presorted) 
+  if (!subcase_is_empty (&agr.sort) && !presorted)
     {
-      input = sort_execute (input, agr.sort);
-      agr.sort = NULL; 
+      input = sort_execute (input, &agr.sort);
+      subcase_clear (&agr.sort);
     }
 
   for (grouper = casegrouper_create_vars (input, agr.break_vars,
                                           agr.break_var_cnt);
        casegrouper_get_next_group (grouper, &group);
-       casereader_destroy (group)) 
+       casereader_destroy (group))
     {
-      struct ccase c;
-      
-      if (!casereader_peek (group, 0, &c))
-        continue;
-      initialize_aggregate_info (&agr, &c);
-      case_destroy (&c);
-
-      for (; casereader_read (group, &c); case_destroy (&c)) 
-        accumulate_aggregate_info (&agr, &c);
-      dump_aggregate_info (&agr, output);
+      struct casereader *placeholder = NULL;
+      struct ccase *c = casereader_peek (group, 0);
+
+      if (c == NULL)
+        {
+          casereader_destroy (group);
+          continue;
+        }
+
+      initialize_aggregate_info (&agr);
+
+      if ( agr.add_variables )
+       placeholder = casereader_clone (group);
+
+      {
+       struct ccase *cg;
+       for (; (cg = casereader_read (group)) != NULL; case_unref (cg))
+         accumulate_aggregate_info (&agr, cg);
+      }
+
+
+      if  (agr.add_variables)
+       {
+         struct ccase *cg;
+         for (; (cg = casereader_read (placeholder)) != NULL; case_unref (cg))
+           dump_aggregate_info (&agr, output, cg);
+
+         casereader_destroy (placeholder);
+       }
+      else
+       {
+         dump_aggregate_info (&agr, output, c);
+         case_unref (c);
+       }
     }
   if (!casegrouper_destroy (grouper))
     goto error;
 
-  if (!proc_commit (ds)) 
+  if (!proc_commit (ds))
     {
       input = NULL;
       goto error;
     }
   input = NULL;
 
-  if (out_file == NULL) 
+  if (out_file == NULL)
     {
       struct casereader *next_input = casewriter_make_reader (output);
       if (next_input == NULL)
         goto error;
-      
+
       proc_set_active_file (ds, next_input, agr.dict);
       agr.dict = NULL;
     }
-  else 
+  else
     {
       ok = casewriter_destroy (output);
       output = NULL;
       if (!ok)
         goto error;
     }
-  
+
   agr_destroy (&agr);
+  fh_unref (out_file);
   return CMD_SUCCESS;
 
 error:
@@ -336,12 +371,14 @@ error:
     proc_commit (ds);
   casewriter_destroy (output);
   agr_destroy (&agr);
+  fh_unref (out_file);
   return CMD_CASCADING_FAILURE;
 }
 
 /* Parse all the aggregate functions. */
 static bool
-parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, struct agr_proc *agr)
+parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict,
+                          struct agr_proc *agr)
 {
   struct agr_var *tail; /* Tail of linked list starting at agr->vars. */
 
@@ -376,12 +413,13 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
       ds_init_empty (&function_name);
 
       /* Parse the list of target variables. */
-      while (!lex_match (lexer, '='))
+      while (!lex_match (lexer, T_EQUALS))
        {
          size_t n_dest_prev = n_dest;
-         
-         if (!parse_DATA_LIST_vars (lexer, &dest, &n_dest,
-                                     PV_APPEND | PV_SINGLE | PV_NO_SCRATCH))
+
+         if (!parse_DATA_LIST_vars (lexer, dict, &dest, &n_dest,
+                                     (PV_APPEND | PV_SINGLE | PV_NO_SCRATCH
+                                      | PV_NO_DUPLICATE)))
            goto error;
 
          /* Assign empty labels. */
@@ -394,16 +432,11 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
          }
 
 
-         
-         if (lex_token (lexer) == T_STRING)
-           {
-             struct string label;
-             ds_init_string (&label, lex_tokstr (lexer));
 
-             ds_truncate (&label, 255);
-             dest_label[n_dest - 1] = ds_xstrdup (&label);
+         if (lex_is_string (lexer))
+           {
+             dest_label[n_dest - 1] = xstrdup (lex_tokcstr (lexer));
              lex_get (lexer);
-             ds_destroy (&label);
            }
        }
 
@@ -414,21 +447,15 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
          goto error;
        }
 
-      exclude = MV_ANY;
-
-      ds_assign_string (&function_name, lex_tokstr (lexer));
-
-      ds_chomp (&function_name, '.');
-
-      if (lex_tokid(lexer)[strlen (lex_tokid (lexer)) - 1] == '.')
-        exclude = MV_SYSTEM;
+      ds_assign_substring (&function_name, lex_tokss (lexer));
+      exclude = ds_chomp_byte (&function_name, '.') ? MV_SYSTEM : MV_ANY;
 
       for (function = agr_func_tab; function->name; function++)
        if (!strcasecmp (function->name, ds_cstr (&function_name)))
          break;
       if (NULL == function->name)
        {
-         msg (SE, _("Unknown aggregation function %s."), 
+         msg (SE, _("Unknown aggregation function %s."),
               ds_cstr (&function_name));
          goto error;
        }
@@ -437,15 +464,11 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
       lex_get (lexer);
 
       /* Check for leading lparen. */
-      if (!lex_match (lexer, '('))
+      if (!lex_match (lexer, T_LPAREN))
        {
-         if (func_index == N)
-           func_index = N_NO_VARS;
-         else if (func_index == NU)
-           func_index = NU_NO_VARS;
-         else
+         if (function->src_vars == AGR_SV_YES)
            {
-             lex_error (lexer, _("expecting `('"));
+              lex_force_match (lexer, T_LPAREN);
              goto error;
            }
        }
@@ -470,25 +493,27 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
            for (i = 0; i < function->n_args; i++)
              {
                int type;
-           
-               lex_match (lexer, ',');
-               if (lex_token (lexer) == T_STRING)
+
+               lex_match (lexer, T_COMMA);
+               if (lex_is_string (lexer))
                  {
-                   arg[i].c = ds_xstrdup (lex_tokstr (lexer));
-                   type = VAR_STRING;
+                   arg[i].c = recode_string (dict_get_encoding (agr->dict),
+                                              "UTF-8", lex_tokcstr (lexer),
+                                              -1);
+                   type = VAL_STRING;
                  }
                else if (lex_is_number (lexer))
                  {
                    arg[i].f = lex_tokval (lexer);
-                   type = VAR_NUMERIC;
+                   type = VAL_NUMERIC;
                  }
                 else
                   {
-                   msg (SE, _("Missing argument %d to %s."),
-                         (int) i + 1, function->name);
+                   msg (SE, _("Missing argument %zu to %s."),
+                         i + 1, function->name);
                    goto error;
                  }
-           
+
                lex_get (lexer);
 
                if (type != var_get_type (src[0]))
@@ -501,12 +526,9 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
              }
 
          /* Trailing rparen. */
-         if (!lex_match (lexer, ')'))
-           {
-             lex_error (lexer, _("expecting `)'"));
-             goto error;
-           }
-         
+         if (!lex_force_match (lexer, T_RPAREN))
+            goto error;
+
          /* Now check that the number of source variables match
             the number of target variables.  If we check earlier
             than this, the user can get very misleading error
@@ -515,14 +537,14 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
             like `unknown variable t'. */
          if (n_src != n_dest)
            {
-             msg (SE, _("Number of source variables (%u) does not match "
-                        "number of target variables (%u)."),
-                  (unsigned) n_src, (unsigned) n_dest);
+             msg (SE, _("Number of source variables (%zu) does not match "
+                        "number of target variables (%zu)."),
+                   n_src, n_dest);
              goto error;
            }
 
           if ((func_index == PIN || func_index == POUT
-              || func_index == FIN || func_index == FOUT) 
+              || func_index == FIN || func_index == FOUT)
               && (var_is_numeric (src[0])
                   ? arg[0].f > arg[1].f
                   : str_compare_rpad (arg[0].c, arg[1].c) > 0))
@@ -530,19 +552,19 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
               union agr_argument t = arg[0];
               arg[0] = arg[1];
               arg[1] = t;
-                  
+
               msg (SW, _("The value arguments passed to the %s function "
                          "are out-of-order.  They will be treated as if "
                          "they had been specified in the correct order."),
                    function->name);
             }
        }
-       
+
       /* Finally add these to the linked list of aggregation
          variables. */
       for (i = 0; i < n_dest; i++)
        {
-         struct agr_var *v = xmalloc (sizeof *v);
+         struct agr_var *v = xzalloc (sizeof *v);
 
          /* Add variable to chain. */
          if (agr->agr_vars != NULL)
@@ -552,37 +574,37 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
           tail = v;
          tail->next = NULL;
           v->moments = NULL;
-         
+
          /* Create the target variable in the aggregate
              dictionary. */
          {
            struct variable *destvar;
-           
+
            v->function = func_index;
 
            if (src)
              {
                v->src = src[i];
-               
+
                if (var_is_alpha (src[i]))
                  {
                    v->function |= FSTRING;
                    v->string = xmalloc (var_get_width (src[i]));
                  }
 
-               if (function->alpha_type == VAR_STRING)
-                 destvar = dict_clone_var (agr->dict, v->src, dest[i]);
+               if (function->alpha_type == VAL_STRING)
+                 destvar = dict_clone_var_as (agr->dict, v->src, dest[i]);
                else
                   {
                     assert (var_is_numeric (v->src)
-                            || function->alpha_type == VAR_NUMERIC);
+                            || function->alpha_type == VAL_NUMERIC);
                     destvar = dict_create_var (agr->dict, dest[i], 0);
-                    if (destvar != NULL) 
+                    if (destvar != NULL)
                       {
                         struct fmt_spec f;
                         if ((func_index == N || func_index == NMISS)
                             && dict_get_weight (dict) != NULL)
-                          f = fmt_for_output (FMT_F, 8, 2); 
+                          f = fmt_for_output (FMT_F, 8, 2);
                         else
                           f = function->format;
                         var_set_both_formats (destvar, &f);
@@ -592,13 +614,17 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
                 struct fmt_spec f;
                v->src = NULL;
                destvar = dict_create_var (agr->dict, dest[i], 0);
-                if (func_index == N_NO_VARS && dict_get_weight (dict) != NULL)
-                  f = fmt_for_output (FMT_F, 8, 2); 
-                else
-                  f = function->format;
-                var_set_both_formats (destvar, &f);
-             }
-         
+               if (destvar != NULL)
+                 {
+                   if ((func_index == N || func_index == NMISS)
+                       && dict_get_weight (dict) != NULL)
+                     f = fmt_for_output (FMT_F, 8, 2);
+                   else
+                     f = function->format;
+                   var_set_both_formats (destvar, &f);
+                 }
+           }
+
            if (!destvar)
              {
                msg (SE, _("Variable name %s is not unique within the "
@@ -611,11 +637,12 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
 
            free (dest[i]);
            if (dest_label[i])
-              var_set_label (destvar, dest_label[i]);
+              var_set_label (destvar, dest_label[i],
+                             dict_get_encoding (agr->dict), true);
 
            v->dest = destvar;
          }
-         
+
          v->exclude = exclude;
 
          if (v->src != NULL)
@@ -630,7 +657,7 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
                  v->arg[j].c = xstrdup (arg[j].c);
            }
        }
-      
+
       if (src != NULL && var_is_alpha (src[0]))
        for (i = 0; i < function->n_args; i++)
          {
@@ -642,16 +669,16 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
       free (dest);
       free (dest_label);
 
-      if (!lex_match (lexer, '/'))
+      if (!lex_match (lexer, T_SLASH))
        {
-         if (lex_token (lexer) == '.')
+         if (lex_token (lexer) == T_ENDCMD)
            return true;
 
          lex_error (lexer, "expecting end of command");
          return false;
        }
       continue;
-      
+
     error:
       ds_destroy (&function_name);
       for (i = 0; i < n_dest; i++)
@@ -670,7 +697,7 @@ parse_aggregate_functions (struct lexer *lexer, const struct dictionary *dict, s
            arg[i].c = NULL;
          }
       free (src);
-       
+
       return false;
     }
 }
@@ -681,9 +708,8 @@ agr_destroy (struct agr_proc *agr)
 {
   struct agr_var *iter, *next;
 
-  case_ordering_destroy (agr->sort);
+  subcase_destroy (&agr->sort);
   free (agr->break_vars);
-  case_destroy (&agr->break_case);
   for (iter = agr->agr_vars; iter; iter = next)
     {
       next = iter->next;
@@ -700,6 +726,10 @@ agr_destroy (struct agr_proc *agr)
        }
       else if (iter->function == SD)
         moments1_destroy (iter->moments);
+
+      dict_destroy_internal_var (iter->subject);
+      dict_destroy_internal_var (iter->weight);
+
       free (iter);
     }
   if (agr->dict != NULL)
@@ -709,7 +739,7 @@ agr_destroy (struct agr_proc *agr)
 /* Execution. */
 
 /* Accumulates aggregation data from the case INPUT. */
-static void 
+static void
 accumulate_aggregate_info (struct agr_proc *agr, const struct ccase *input)
 {
   struct agr_var *iter;
@@ -740,7 +770,7 @@ accumulate_aggregate_info (struct agr_proc *agr, const struct ccase *input)
            iter->saw_missing = true;
            continue;
          }
-       
+
        /* This is horrible.  There are too many possibilities. */
        switch (iter->function)
          {
@@ -752,6 +782,25 @@ accumulate_aggregate_info (struct agr_proc *agr, const struct ccase *input)
             iter->dbl[0] += v->f * weight;
             iter->dbl[1] += weight;
             break;
+         case MEDIAN:
+           {
+             double wv ;
+             struct ccase *cout;
+
+              cout = case_create (casewriter_get_proto (iter->writer));
+
+             case_data_rw (cout, iter->subject)->f
+                = case_data (input, iter->src)->f;
+
+             wv = dict_get_case_weight (agr->src_dict, input, NULL);
+
+             case_data_rw (cout, iter->weight)->f = wv;
+
+             iter->cc += wv;
+
+             casewriter_write (iter->writer, cout);
+           }
+           break;
          case SD:
             moments1_add (iter->moments, v->f, weight);
             break;
@@ -760,8 +809,9 @@ accumulate_aggregate_info (struct agr_proc *agr, const struct ccase *input)
            iter->int1 = 1;
            break;
          case MAX | FSTRING:
-           if (memcmp (iter->string, v->s, src_width) < 0)
-             memcpy (iter->string, v->s, src_width);
+            /* Need to do some kind of Unicode collation thingy here */
+           if (memcmp (iter->string, value_str (v, src_width), src_width) < 0)
+             memcpy (iter->string, value_str (v, src_width), src_width);
            iter->int1 = 1;
            break;
          case MIN:
@@ -769,8 +819,8 @@ accumulate_aggregate_info (struct agr_proc *agr, const struct ccase *input)
            iter->int1 = 1;
            break;
          case MIN | FSTRING:
-           if (memcmp (iter->string, v->s, src_width) > 0)
-             memcpy (iter->string, v->s, src_width);
+           if (memcmp (iter->string, value_str (v, src_width), src_width) > 0)
+             memcpy (iter->string, value_str (v, src_width), src_width);
            iter->int1 = 1;
            break;
          case FGT:
@@ -781,7 +831,8 @@ accumulate_aggregate_info (struct agr_proc *agr, const struct ccase *input)
             break;
          case FGT | FSTRING:
          case PGT | FSTRING:
-            if (memcmp (iter->arg[0].c, v->s, src_width) < 0)
+            if (memcmp (iter->arg[0].c,
+                        value_str (v, src_width), src_width) < 0)
               iter->dbl[0] += weight;
             iter->dbl[1] += weight;
             break;
@@ -793,7 +844,8 @@ accumulate_aggregate_info (struct agr_proc *agr, const struct ccase *input)
             break;
          case FLT | FSTRING:
          case PLT | FSTRING:
-            if (memcmp (iter->arg[0].c, v->s, src_width) > 0)
+            if (memcmp (iter->arg[0].c,
+                        value_str (v, src_width), src_width) > 0)
               iter->dbl[0] += weight;
             iter->dbl[1] += weight;
             break;
@@ -805,8 +857,10 @@ accumulate_aggregate_info (struct agr_proc *agr, const struct ccase *input)
             break;
          case FIN | FSTRING:
          case PIN | FSTRING:
-            if (memcmp (iter->arg[0].c, v->s, src_width) <= 0
-                && memcmp (iter->arg[1].c, v->s, src_width) >= 0)
+            if (memcmp (iter->arg[0].c,
+                        value_str (v, src_width), src_width) <= 0
+                && memcmp (iter->arg[1].c,
+                           value_str (v, src_width), src_width) >= 0)
               iter->dbl[0] += weight;
             iter->dbl[1] += weight;
             break;
@@ -818,8 +872,10 @@ accumulate_aggregate_info (struct agr_proc *agr, const struct ccase *input)
             break;
          case FOUT | FSTRING:
          case POUT | FSTRING:
-            if (memcmp (iter->arg[0].c, v->s, src_width) > 0
-                || memcmp (iter->arg[1].c, v->s, src_width) < 0)
+            if (memcmp (iter->arg[0].c,
+                        value_str (v, src_width), src_width) > 0
+                || memcmp (iter->arg[1].c,
+                           value_str (v, src_width), src_width) < 0)
               iter->dbl[0] += weight;
             iter->dbl[1] += weight;
             break;
@@ -841,7 +897,7 @@ accumulate_aggregate_info (struct agr_proc *agr, const struct ccase *input)
          case FIRST | FSTRING:
            if (iter->int1 == 0)
              {
-               memcpy (iter->string, v->s, src_width);
+               memcpy (iter->string, value_str (v, src_width), src_width);
                iter->int1 = 1;
              }
            break;
@@ -850,7 +906,7 @@ accumulate_aggregate_info (struct agr_proc *agr, const struct ccase *input)
            iter->int1 = 1;
            break;
          case LAST | FSTRING:
-           memcpy (iter->string, v->s, src_width);
+           memcpy (iter->string, value_str (v, src_width), src_width);
            iter->int1 = 1;
            break;
           case NMISS:
@@ -863,13 +919,13 @@ accumulate_aggregate_info (struct agr_proc *agr, const struct ccase *input)
          default:
            NOT_REACHED ();
          }
-    } else {
+      } else {
       switch (iter->function)
        {
-       case N_NO_VARS:
+       case N:
          iter->dbl[0] += weight;
          break;
-       case NU_NO_VARS:
+       case NU:
          iter->int1++;
          break;
        default:
@@ -879,46 +935,47 @@ accumulate_aggregate_info (struct agr_proc *agr, const struct ccase *input)
 }
 
 /* Writes an aggregated record to OUTPUT. */
-static void 
-dump_aggregate_info (struct agr_proc *agr, struct casewriter *output)
+static void
+dump_aggregate_info (const struct agr_proc *agr, struct casewriter *output, const struct ccase *break_case)
 {
-  struct ccase c;
+  struct ccase *c = case_create (dict_get_proto (agr->dict));
 
-  case_create (&c, dict_get_next_value_idx (agr->dict));
+  if ( agr->add_variables)
+    {
+      case_copy (c, 0, break_case, 0, dict_get_var_cnt (agr->src_dict));
+    }
+  else
+    {
+      int value_idx = 0;
+      int i;
 
-  {
-    int value_idx = 0;
-    int i;
+      for (i = 0; i < agr->break_var_cnt; i++)
+       {
+         const struct variable *v = agr->break_vars[i];
+         value_copy (case_data_rw_idx (c, value_idx),
+                     case_data (break_case, v),
+                     var_get_width (v));
+         value_idx++;
+       }
+    }
 
-    for (i = 0; i < agr->break_var_cnt; i++) 
-      {
-        const struct variable *v = agr->break_vars[i];
-        size_t value_cnt = var_get_value_cnt (v);
-        memcpy (case_data_rw_idx (&c, value_idx),
-                case_data (&agr->break_case, v),
-                sizeof (union value) * value_cnt);
-        value_idx += value_cnt; 
-      }
-  }
-  
   {
     struct agr_var *i;
-  
+
     for (i = agr->agr_vars; i; i = i->next)
       {
-       union value *v = case_data_rw (&c, i->dest);
+       union value *v = case_data_rw (c, i->dest);
+        int width = var_get_width (i->dest);
 
        if (agr->missing == COLUMNWISE && i->saw_missing
            && (i->function & FUNC) != N && (i->function & FUNC) != NU
            && (i->function & FUNC) != NMISS && (i->function & FUNC) != NUMISS)
          {
-           if (var_is_alpha (i->dest))
-             memset (v->s, ' ', var_get_width (i->dest));
-           else
-             v->f = SYSMIS;
+            value_set_missing (v, width);
+           casewriter_destroy (i->writer);
            continue;
          }
-       
+
        switch (i->function)
          {
          case SUM:
@@ -927,6 +984,26 @@ dump_aggregate_info (struct agr_proc *agr, struct casewriter *output)
          case MEAN:
            v->f = i->dbl[1] != 0.0 ? i->dbl[0] / i->dbl[1] : SYSMIS;
            break;
+         case MEDIAN:
+           {
+             if ( i->writer)
+               {
+                 struct percentile *median = percentile_create (0.5, i->cc);
+                 struct order_stats *os = &median->parent;
+                 struct casereader *sorted_reader = casewriter_make_reader (i->writer);
+                 i->writer = NULL;
+
+                 order_stats_accumulate (&os, 1,
+                                         sorted_reader,
+                                         i->weight,
+                                         i->subject,
+                                         i->exclude);
+                 i->dbl[0] = percentile_calculate (median, PC_HAVERAGE);
+                 statistic_destroy (&median->parent.parent);
+               }
+             v->f = i->dbl[0];
+           }
+           break;
          case SD:
             {
               double variance;
@@ -937,7 +1014,7 @@ dump_aggregate_info (struct agr_proc *agr, struct casewriter *output)
               if (variance != SYSMIS)
                 v->f = sqrt (variance);
               else
-                v->f = SYSMIS; 
+                v->f = SYSMIS;
             }
            break;
          case MAX:
@@ -947,9 +1024,9 @@ dump_aggregate_info (struct agr_proc *agr, struct casewriter *output)
          case MAX | FSTRING:
          case MIN | FSTRING:
            if (i->int1)
-             memcpy (v->s, i->string, var_get_width (i->dest));
+             memcpy (value_str_rw (v, width), i->string, width);
            else
-             memset (v->s, ' ', var_get_width (i->dest));
+              value_set_missing (v, width);
            break;
          case FGT:
          case FGT | FSTRING:
@@ -973,7 +1050,7 @@ dump_aggregate_info (struct agr_proc *agr, struct casewriter *output)
            break;
          case N:
          case N | FSTRING:
-           v->f = i->dbl[0];
+             v->f = i->dbl[0];
             break;
          case NU:
          case NU | FSTRING:
@@ -986,15 +1063,9 @@ dump_aggregate_info (struct agr_proc *agr, struct casewriter *output)
          case FIRST | FSTRING:
          case LAST | FSTRING:
            if (i->int1)
-             memcpy (v->s, i->string, var_get_width (i->dest));
+             memcpy (value_str_rw (v, width), i->string, width);
            else
-             memset (v->s, ' ', var_get_width (i->dest));
-           break;
-         case N_NO_VARS:
-           v->f = i->dbl[0];
-           break;
-         case NU_NO_VARS:
-           v->f = i->int1;
+              value_set_missing (v, width);
            break;
          case NMISS:
          case NMISS | FSTRING:
@@ -1010,18 +1081,15 @@ dump_aggregate_info (struct agr_proc *agr, struct casewriter *output)
       }
   }
 
-  casewriter_write (output, &c);
+  casewriter_write (output, c);
 }
 
 /* Resets the state for all the aggregate functions. */
 static void
-initialize_aggregate_info (struct agr_proc *agr, const struct ccase *input)
+initialize_aggregate_info (struct agr_proc *agr)
 {
   struct agr_var *iter;
 
-  case_destroy (&agr->break_case);
-  case_clone (&agr->break_case, input);
-
   for (iter = agr->agr_vars; iter; iter = iter->next)
     {
       iter->saw_missing = false;
@@ -1041,6 +1109,29 @@ initialize_aggregate_info (struct agr_proc *agr, const struct ccase *input)
        case MAX | FSTRING:
          memset (iter->string, 0, var_get_width (iter->src));
          break;
+       case MEDIAN:
+         {
+            struct caseproto *proto;
+            struct subcase ordering;
+
+            proto = caseproto_create ();
+            proto = caseproto_add_width (proto, 0);
+            proto = caseproto_add_width (proto, 0);
+
+           if ( ! iter->subject)
+             iter->subject = dict_create_internal_var (0, 0);
+
+           if ( ! iter->weight)
+             iter->weight = dict_create_internal_var (1, 0);
+
+            subcase_init_var (&ordering, iter->subject, SC_ASCEND);
+           iter->writer = sort_create_writer (&ordering, proto);
+            subcase_destroy (&ordering);
+            caseproto_unref (proto);
+
+           iter->cc = 0;
+         }
+         break;
         case SD:
           if (iter->moments == NULL)
             iter->moments = moments1_create (MOMENT_VARIANCE);